aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format49
-rw-r--r--.gitignore44
-rw-r--r--CMakeLists.txt232
-rw-r--r--README28
-rw-r--r--clang-tidy.conf8
-rw-r--r--contrib/catch2/.bazelrc (renamed from .bazelrc)0
-rw-r--r--contrib/catch2/.clang-format45
-rw-r--r--contrib/catch2/.clang-tidy (renamed from .clang-tidy)0
-rw-r--r--contrib/catch2/.conan/build.py (renamed from .conan/build.py)0
-rw-r--r--contrib/catch2/.conan/test_package/CMakeLists.txt (renamed from .conan/test_package/CMakeLists.txt)0
-rw-r--r--contrib/catch2/.conan/test_package/conanfile.py (renamed from .conan/test_package/conanfile.py)0
-rw-r--r--contrib/catch2/.conan/test_package/test_package.cpp (renamed from .conan/test_package/test_package.cpp)0
-rw-r--r--contrib/catch2/.gitattributes (renamed from .gitattributes)0
-rw-r--r--contrib/catch2/.github/FUNDING.yml (renamed from .github/FUNDING.yml)0
-rw-r--r--contrib/catch2/.github/ISSUE_TEMPLATE/bug_report.md (renamed from .github/ISSUE_TEMPLATE/bug_report.md)0
-rw-r--r--contrib/catch2/.github/ISSUE_TEMPLATE/feature_request.md (renamed from .github/ISSUE_TEMPLATE/feature_request.md)0
-rw-r--r--contrib/catch2/.github/pull_request_template.md (renamed from .github/pull_request_template.md)0
-rw-r--r--contrib/catch2/.github/workflows/linux-bazel-builds.yml (renamed from .github/workflows/linux-bazel-builds.yml)0
-rw-r--r--contrib/catch2/.github/workflows/linux-meson-builds.yml (renamed from .github/workflows/linux-meson-builds.yml)0
-rw-r--r--contrib/catch2/.github/workflows/linux-other-builds.yml (renamed from .github/workflows/linux-other-builds.yml)0
-rw-r--r--contrib/catch2/.github/workflows/linux-simple-builds.yml (renamed from .github/workflows/linux-simple-builds.yml)0
-rw-r--r--contrib/catch2/.github/workflows/mac-builds-m1.yml (renamed from .github/workflows/mac-builds-m1.yml)0
-rw-r--r--contrib/catch2/.github/workflows/mac-builds.yml (renamed from .github/workflows/mac-builds.yml)0
-rw-r--r--contrib/catch2/.github/workflows/package-manager-builds.yaml (renamed from .github/workflows/package-manager-builds.yaml)0
-rw-r--r--contrib/catch2/.github/workflows/validate-header-guards.yml (renamed from .github/workflows/validate-header-guards.yml)0
-rw-r--r--contrib/catch2/.github/workflows/windows-simple-builds.yml (renamed from .github/workflows/windows-simple-builds.yml)0
-rw-r--r--contrib/catch2/.gitignore40
-rw-r--r--contrib/catch2/BUILD.bazel (renamed from BUILD.bazel)0
-rw-r--r--contrib/catch2/CMake/Catch2Config.cmake.in (renamed from CMake/Catch2Config.cmake.in)0
-rw-r--r--contrib/catch2/CMake/CatchConfigOptions.cmake (renamed from CMake/CatchConfigOptions.cmake)0
-rw-r--r--contrib/catch2/CMake/CatchMiscFunctions.cmake (renamed from CMake/CatchMiscFunctions.cmake)0
-rw-r--r--contrib/catch2/CMake/FindGcov.cmake (renamed from CMake/FindGcov.cmake)0
-rw-r--r--contrib/catch2/CMake/FindLcov.cmake (renamed from CMake/FindLcov.cmake)0
-rw-r--r--contrib/catch2/CMake/Findcodecov.cmake (renamed from CMake/Findcodecov.cmake)0
-rw-r--r--contrib/catch2/CMake/catch2-with-main.pc.in (renamed from CMake/catch2-with-main.pc.in)0
-rw-r--r--contrib/catch2/CMake/catch2.pc.in (renamed from CMake/catch2.pc.in)0
-rwxr-xr-xcontrib/catch2/CMake/llvm-cov-wrapper (renamed from CMake/llvm-cov-wrapper)0
-rw-r--r--contrib/catch2/CMakeLists.txt198
-rw-r--r--contrib/catch2/CMakePresets.json (renamed from CMakePresets.json)0
-rw-r--r--contrib/catch2/CODE_OF_CONDUCT.md (renamed from CODE_OF_CONDUCT.md)0
-rw-r--r--contrib/catch2/Doxyfile (renamed from Doxyfile)0
-rw-r--r--contrib/catch2/LICENSE.txt (renamed from LICENSE.txt)0
-rw-r--r--contrib/catch2/MODULE.bazel (renamed from MODULE.bazel)0
-rw-r--r--contrib/catch2/README.md (renamed from README.md)0
-rw-r--r--contrib/catch2/SECURITY.md (renamed from SECURITY.md)0
-rw-r--r--contrib/catch2/appveyor.yml (renamed from appveyor.yml)0
-rw-r--r--contrib/catch2/codecov.yml (renamed from codecov.yml)0
-rwxr-xr-xcontrib/catch2/conanfile.py (renamed from conanfile.py)0
-rw-r--r--contrib/catch2/data/artwork/catch2-c-logo.png (renamed from data/artwork/catch2-c-logo.png)bin10636 -> 10636 bytes
-rw-r--r--contrib/catch2/data/artwork/catch2-hand-logo.png (renamed from data/artwork/catch2-hand-logo.png)bin33761 -> 33761 bytes
-rw-r--r--contrib/catch2/data/artwork/catch2-logo-small-with-background.png (renamed from data/artwork/catch2-logo-small-with-background.png)bin25330 -> 25330 bytes
-rw-r--r--contrib/catch2/data/artwork/catch2-logo-small.png (renamed from data/artwork/catch2-logo-small.png)bin20939 -> 20939 bytes
-rw-r--r--contrib/catch2/docs/Readme.md (renamed from docs/Readme.md)0
-rw-r--r--contrib/catch2/docs/assertions.md (renamed from docs/assertions.md)0
-rw-r--r--contrib/catch2/docs/benchmarks.md (renamed from docs/benchmarks.md)0
-rw-r--r--contrib/catch2/docs/ci-and-misc.md (renamed from docs/ci-and-misc.md)0
-rw-r--r--contrib/catch2/docs/cmake-integration.md (renamed from docs/cmake-integration.md)0
-rw-r--r--contrib/catch2/docs/command-line.md (renamed from docs/command-line.md)0
-rw-r--r--contrib/catch2/docs/commercial-users.md (renamed from docs/commercial-users.md)0
-rw-r--r--contrib/catch2/docs/comparing-floating-point-numbers.md (renamed from docs/comparing-floating-point-numbers.md)0
-rw-r--r--contrib/catch2/docs/configuration.md (renamed from docs/configuration.md)0
-rw-r--r--contrib/catch2/docs/contributing.md (renamed from docs/contributing.md)0
-rw-r--r--contrib/catch2/docs/deprecations.md (renamed from docs/deprecations.md)0
-rw-r--r--contrib/catch2/docs/event-listeners.md (renamed from docs/event-listeners.md)0
-rw-r--r--contrib/catch2/docs/faq.md (renamed from docs/faq.md)0
-rw-r--r--contrib/catch2/docs/generators.md (renamed from docs/generators.md)0
-rw-r--r--contrib/catch2/docs/limitations.md (renamed from docs/limitations.md)0
-rw-r--r--contrib/catch2/docs/list-of-examples.md (renamed from docs/list-of-examples.md)0
-rw-r--r--contrib/catch2/docs/logging.md (renamed from docs/logging.md)0
-rw-r--r--contrib/catch2/docs/matchers.md (renamed from docs/matchers.md)0
-rw-r--r--contrib/catch2/docs/migrate-v2-to-v3.md (renamed from docs/migrate-v2-to-v3.md)0
-rw-r--r--contrib/catch2/docs/opensource-users.md (renamed from docs/opensource-users.md)0
-rw-r--r--contrib/catch2/docs/other-macros.md (renamed from docs/other-macros.md)0
-rw-r--r--contrib/catch2/docs/own-main.md (renamed from docs/own-main.md)0
-rw-r--r--contrib/catch2/docs/release-notes.md (renamed from docs/release-notes.md)0
-rw-r--r--contrib/catch2/docs/release-process.md (renamed from docs/release-process.md)0
-rw-r--r--contrib/catch2/docs/reporter-events.md (renamed from docs/reporter-events.md)0
-rw-r--r--contrib/catch2/docs/reporters.md (renamed from docs/reporters.md)0
-rw-r--r--contrib/catch2/docs/skipping-passing-failing.md (renamed from docs/skipping-passing-failing.md)0
-rw-r--r--contrib/catch2/docs/test-cases-and-sections.md (renamed from docs/test-cases-and-sections.md)0
-rw-r--r--contrib/catch2/docs/test-fixtures.md (renamed from docs/test-fixtures.md)0
-rw-r--r--contrib/catch2/docs/tostring.md (renamed from docs/tostring.md)0
-rw-r--r--contrib/catch2/docs/tutorial.md (renamed from docs/tutorial.md)0
-rw-r--r--contrib/catch2/docs/usage-tips.md (renamed from docs/usage-tips.md)0
-rw-r--r--contrib/catch2/docs/why-catch.md (renamed from docs/why-catch.md)0
-rw-r--r--contrib/catch2/examples/010-TestCase.cpp (renamed from examples/010-TestCase.cpp)0
-rw-r--r--contrib/catch2/examples/020-TestCase-1.cpp (renamed from examples/020-TestCase-1.cpp)0
-rw-r--r--contrib/catch2/examples/020-TestCase-2.cpp (renamed from examples/020-TestCase-2.cpp)0
-rw-r--r--contrib/catch2/examples/030-Asn-Require-Check.cpp (renamed from examples/030-Asn-Require-Check.cpp)0
-rw-r--r--contrib/catch2/examples/100-Fix-Section.cpp (renamed from examples/100-Fix-Section.cpp)0
-rw-r--r--contrib/catch2/examples/110-Fix-ClassFixture.cpp (renamed from examples/110-Fix-ClassFixture.cpp)0
-rw-r--r--contrib/catch2/examples/111-Fix-PersistentFixture.cpp (renamed from examples/111-Fix-PersistentFixture.cpp)0
-rw-r--r--contrib/catch2/examples/120-Bdd-ScenarioGivenWhenThen.cpp (renamed from examples/120-Bdd-ScenarioGivenWhenThen.cpp)0
-rw-r--r--contrib/catch2/examples/210-Evt-EventListeners.cpp (renamed from examples/210-Evt-EventListeners.cpp)0
-rw-r--r--contrib/catch2/examples/231-Cfg-OutputStreams.cpp (renamed from examples/231-Cfg-OutputStreams.cpp)0
-rw-r--r--contrib/catch2/examples/232-Cfg-CustomMain.cpp (renamed from examples/232-Cfg-CustomMain.cpp)0
-rw-r--r--contrib/catch2/examples/300-Gen-OwnGenerator.cpp (renamed from examples/300-Gen-OwnGenerator.cpp)0
-rw-r--r--contrib/catch2/examples/301-Gen-MapTypeConversion.cpp (renamed from examples/301-Gen-MapTypeConversion.cpp)0
-rw-r--r--contrib/catch2/examples/302-Gen-Table.cpp (renamed from examples/302-Gen-Table.cpp)0
-rw-r--r--contrib/catch2/examples/310-Gen-VariablesInGenerators.cpp (renamed from examples/310-Gen-VariablesInGenerators.cpp)0
-rw-r--r--contrib/catch2/examples/311-Gen-CustomCapture.cpp (renamed from examples/311-Gen-CustomCapture.cpp)0
-rw-r--r--contrib/catch2/examples/CMakeLists.txt (renamed from examples/CMakeLists.txt)0
-rw-r--r--contrib/catch2/extras/Catch.cmake (renamed from extras/Catch.cmake)0
-rw-r--r--contrib/catch2/extras/CatchAddTests.cmake (renamed from extras/CatchAddTests.cmake)0
-rw-r--r--contrib/catch2/extras/CatchShardTests.cmake (renamed from extras/CatchShardTests.cmake)0
-rw-r--r--contrib/catch2/extras/CatchShardTestsImpl.cmake (renamed from extras/CatchShardTestsImpl.cmake)0
-rw-r--r--contrib/catch2/extras/ParseAndAddCatchTests.cmake (renamed from extras/ParseAndAddCatchTests.cmake)0
-rw-r--r--contrib/catch2/extras/catch_amalgamated.cpp (renamed from extras/catch_amalgamated.cpp)0
-rw-r--r--contrib/catch2/extras/catch_amalgamated.hpp (renamed from extras/catch_amalgamated.hpp)0
-rw-r--r--contrib/catch2/extras/gdbinit (renamed from extras/gdbinit)0
-rw-r--r--contrib/catch2/extras/lldbinit (renamed from extras/lldbinit)0
-rw-r--r--contrib/catch2/fuzzing/CMakeLists.txt (renamed from fuzzing/CMakeLists.txt)0
-rw-r--r--contrib/catch2/fuzzing/NullOStream.cpp (renamed from fuzzing/NullOStream.cpp)0
-rw-r--r--contrib/catch2/fuzzing/NullOStream.h (renamed from fuzzing/NullOStream.h)0
-rwxr-xr-xcontrib/catch2/fuzzing/build_fuzzers.sh (renamed from fuzzing/build_fuzzers.sh)0
-rw-r--r--contrib/catch2/fuzzing/fuzz_TestSpecParser.cpp (renamed from fuzzing/fuzz_TestSpecParser.cpp)0
-rw-r--r--contrib/catch2/fuzzing/fuzz_XmlWriter.cpp (renamed from fuzzing/fuzz_XmlWriter.cpp)0
-rw-r--r--contrib/catch2/fuzzing/fuzz_textflow.cpp (renamed from fuzzing/fuzz_textflow.cpp)0
-rw-r--r--contrib/catch2/mdsnippets.json (renamed from mdsnippets.json)0
-rw-r--r--contrib/catch2/meson.build (renamed from meson.build)0
-rw-r--r--contrib/catch2/meson_options.txt (renamed from meson_options.txt)0
-rw-r--r--contrib/catch2/src/CMakeLists.txt (renamed from src/CMakeLists.txt)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_benchmark.hpp (renamed from src/catch2/benchmark/catch_benchmark.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_benchmark_all.hpp (renamed from src/catch2/benchmark/catch_benchmark_all.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_chronometer.cpp (renamed from src/catch2/benchmark/catch_chronometer.cpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_chronometer.hpp (renamed from src/catch2/benchmark/catch_chronometer.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_clock.hpp (renamed from src/catch2/benchmark/catch_clock.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_constructor.hpp (renamed from src/catch2/benchmark/catch_constructor.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_environment.hpp (renamed from src/catch2/benchmark/catch_environment.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_estimate.hpp (renamed from src/catch2/benchmark/catch_estimate.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_execution_plan.hpp (renamed from src/catch2/benchmark/catch_execution_plan.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_optimizer.hpp (renamed from src/catch2/benchmark/catch_optimizer.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_outlier_classification.hpp (renamed from src/catch2/benchmark/catch_outlier_classification.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/catch_sample_analysis.hpp (renamed from src/catch2/benchmark/catch_sample_analysis.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_analyse.cpp (renamed from src/catch2/benchmark/detail/catch_analyse.cpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_analyse.hpp (renamed from src/catch2/benchmark/detail/catch_analyse.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.cpp (renamed from src/catch2/benchmark/detail/catch_benchmark_function.cpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.hpp (renamed from src/catch2/benchmark/detail/catch_benchmark_function.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats.hpp (renamed from src/catch2/benchmark/detail/catch_benchmark_stats.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp (renamed from src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_complete_invoke.hpp (renamed from src/catch2/benchmark/detail/catch_complete_invoke.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_estimate_clock.hpp (renamed from src/catch2/benchmark/detail/catch_estimate_clock.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_measure.hpp (renamed from src/catch2/benchmark/detail/catch_measure.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_repeat.hpp (renamed from src/catch2/benchmark/detail/catch_repeat.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.cpp (renamed from src/catch2/benchmark/detail/catch_run_for_at_least.cpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.hpp (renamed from src/catch2/benchmark/detail/catch_run_for_at_least.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_stats.cpp (renamed from src/catch2/benchmark/detail/catch_stats.cpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_stats.hpp (renamed from src/catch2/benchmark/detail/catch_stats.hpp)0
-rw-r--r--contrib/catch2/src/catch2/benchmark/detail/catch_timing.hpp (renamed from src/catch2/benchmark/detail/catch_timing.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_all.hpp (renamed from src/catch2/catch_all.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_approx.cpp (renamed from src/catch2/catch_approx.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_approx.hpp (renamed from src/catch2/catch_approx.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_assertion_info.hpp (renamed from src/catch2/catch_assertion_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_assertion_result.cpp (renamed from src/catch2/catch_assertion_result.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_assertion_result.hpp (renamed from src/catch2/catch_assertion_result.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_case_sensitive.hpp (renamed from src/catch2/catch_case_sensitive.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_config.cpp (renamed from src/catch2/catch_config.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_config.hpp (renamed from src/catch2/catch_config.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_get_random_seed.cpp (renamed from src/catch2/catch_get_random_seed.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_get_random_seed.hpp (renamed from src/catch2/catch_get_random_seed.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_message.cpp (renamed from src/catch2/catch_message.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_message.hpp (renamed from src/catch2/catch_message.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_registry_hub.cpp (renamed from src/catch2/catch_registry_hub.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_section_info.hpp (renamed from src/catch2/catch_section_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_session.cpp (renamed from src/catch2/catch_session.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_session.hpp (renamed from src/catch2/catch_session.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_tag_alias.hpp (renamed from src/catch2/catch_tag_alias.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.cpp (renamed from src/catch2/catch_tag_alias_autoregistrar.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.hpp (renamed from src/catch2/catch_tag_alias_autoregistrar.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_template_test_macros.hpp (renamed from src/catch2/catch_template_test_macros.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_test_case_info.cpp (renamed from src/catch2/catch_test_case_info.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_test_case_info.hpp (renamed from src/catch2/catch_test_case_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_test_macros.hpp (renamed from src/catch2/catch_test_macros.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_test_spec.cpp (renamed from src/catch2/catch_test_spec.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_test_spec.hpp (renamed from src/catch2/catch_test_spec.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_timer.cpp (renamed from src/catch2/catch_timer.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_timer.hpp (renamed from src/catch2/catch_timer.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_tostring.cpp (renamed from src/catch2/catch_tostring.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_tostring.hpp (renamed from src/catch2/catch_tostring.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_totals.cpp (renamed from src/catch2/catch_totals.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_totals.hpp (renamed from src/catch2/catch_totals.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_translate_exception.cpp (renamed from src/catch2/catch_translate_exception.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_translate_exception.hpp (renamed from src/catch2/catch_translate_exception.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_user_config.hpp.in (renamed from src/catch2/catch_user_config.hpp.in)0
-rw-r--r--contrib/catch2/src/catch2/catch_version.cpp (renamed from src/catch2/catch_version.cpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_version.hpp (renamed from src/catch2/catch_version.hpp)0
-rw-r--r--contrib/catch2/src/catch2/catch_version_macros.hpp (renamed from src/catch2/catch_version_macros.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generator_exception.cpp (renamed from src/catch2/generators/catch_generator_exception.cpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generator_exception.hpp (renamed from src/catch2/generators/catch_generator_exception.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators.cpp (renamed from src/catch2/generators/catch_generators.cpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators.hpp (renamed from src/catch2/generators/catch_generators.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators_adapters.hpp (renamed from src/catch2/generators/catch_generators_adapters.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators_all.hpp (renamed from src/catch2/generators/catch_generators_all.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators_random.cpp (renamed from src/catch2/generators/catch_generators_random.cpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators_random.hpp (renamed from src/catch2/generators/catch_generators_random.hpp)0
-rw-r--r--contrib/catch2/src/catch2/generators/catch_generators_range.hpp (renamed from src/catch2/generators/catch_generators_range.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_all.hpp (renamed from src/catch2/interfaces/catch_interfaces_all.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.cpp (renamed from src/catch2/interfaces/catch_interfaces_capture.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.hpp (renamed from src/catch2/interfaces/catch_interfaces_capture.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_config.cpp (renamed from src/catch2/interfaces/catch_interfaces_config.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_config.hpp (renamed from src/catch2/interfaces/catch_interfaces_config.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp (renamed from src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.cpp (renamed from src/catch2/interfaces/catch_interfaces_exception.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.hpp (renamed from src/catch2/interfaces/catch_interfaces_exception.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.cpp (renamed from src/catch2/interfaces/catch_interfaces_generatortracker.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.hpp (renamed from src/catch2/interfaces/catch_interfaces_generatortracker.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.cpp (renamed from src/catch2/interfaces/catch_interfaces_registry_hub.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.hpp (renamed from src/catch2/interfaces/catch_interfaces_registry_hub.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.cpp (renamed from src/catch2/interfaces/catch_interfaces_reporter.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.hpp (renamed from src/catch2/interfaces/catch_interfaces_reporter.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.cpp (renamed from src/catch2/interfaces/catch_interfaces_reporter_factory.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.hpp (renamed from src/catch2/interfaces/catch_interfaces_reporter_factory.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp (renamed from src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_test_invoker.hpp (renamed from src/catch2/interfaces/catch_interfaces_test_invoker.hpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.cpp (renamed from src/catch2/interfaces/catch_interfaces_testcase.cpp)0
-rw-r--r--contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.hpp (renamed from src/catch2/interfaces/catch_interfaces_testcase.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_assertion_handler.cpp (renamed from src/catch2/internal/catch_assertion_handler.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_assertion_handler.hpp (renamed from src/catch2/internal/catch_assertion_handler.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.cpp (renamed from src/catch2/internal/catch_case_insensitive_comparisons.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.hpp (renamed from src/catch2/internal/catch_case_insensitive_comparisons.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_clara.cpp (renamed from src/catch2/internal/catch_clara.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_clara.hpp (renamed from src/catch2/internal/catch_clara.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_commandline.cpp (renamed from src/catch2/internal/catch_commandline.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_commandline.hpp (renamed from src/catch2/internal/catch_commandline.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_compare_traits.hpp (renamed from src/catch2/internal/catch_compare_traits.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_compiler_capabilities.hpp (renamed from src/catch2/internal/catch_compiler_capabilities.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_android_logwrite.hpp (renamed from src/catch2/internal/catch_config_android_logwrite.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_counter.hpp (renamed from src/catch2/internal/catch_config_counter.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_prefix_messages.hpp (renamed from src/catch2/internal/catch_config_prefix_messages.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_static_analysis_support.hpp (renamed from src/catch2/internal/catch_config_static_analysis_support.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_uncaught_exceptions.hpp (renamed from src/catch2/internal/catch_config_uncaught_exceptions.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_config_wchar.hpp (renamed from src/catch2/internal/catch_config_wchar.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_console_colour.cpp (renamed from src/catch2/internal/catch_console_colour.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_console_colour.hpp (renamed from src/catch2/internal/catch_console_colour.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_console_width.hpp (renamed from src/catch2/internal/catch_console_width.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_container_nonmembers.hpp (renamed from src/catch2/internal/catch_container_nonmembers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_context.cpp (renamed from src/catch2/internal/catch_context.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_context.hpp (renamed from src/catch2/internal/catch_context.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_debug_console.cpp (renamed from src/catch2/internal/catch_debug_console.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_debug_console.hpp (renamed from src/catch2/internal/catch_debug_console.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_debugger.cpp (renamed from src/catch2/internal/catch_debugger.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_debugger.hpp (renamed from src/catch2/internal/catch_debugger.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_decomposer.cpp (renamed from src/catch2/internal/catch_decomposer.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_decomposer.hpp (renamed from src/catch2/internal/catch_decomposer.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_enforce.cpp (renamed from src/catch2/internal/catch_enforce.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_enforce.hpp (renamed from src/catch2/internal/catch_enforce.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_enum_values_registry.cpp (renamed from src/catch2/internal/catch_enum_values_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_enum_values_registry.hpp (renamed from src/catch2/internal/catch_enum_values_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_errno_guard.cpp (renamed from src/catch2/internal/catch_errno_guard.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_errno_guard.hpp (renamed from src/catch2/internal/catch_errno_guard.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_exception_translator_registry.cpp (renamed from src/catch2/internal/catch_exception_translator_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_exception_translator_registry.hpp (renamed from src/catch2/internal/catch_exception_translator_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.cpp (renamed from src/catch2/internal/catch_fatal_condition_handler.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.hpp (renamed from src/catch2/internal/catch_fatal_condition_handler.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_floating_point_helpers.cpp (renamed from src/catch2/internal/catch_floating_point_helpers.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_floating_point_helpers.hpp (renamed from src/catch2/internal/catch_floating_point_helpers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_getenv.cpp (renamed from src/catch2/internal/catch_getenv.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_getenv.hpp (renamed from src/catch2/internal/catch_getenv.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_is_permutation.hpp (renamed from src/catch2/internal/catch_is_permutation.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_istream.cpp (renamed from src/catch2/internal/catch_istream.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_istream.hpp (renamed from src/catch2/internal/catch_istream.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_jsonwriter.cpp (renamed from src/catch2/internal/catch_jsonwriter.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_jsonwriter.hpp (renamed from src/catch2/internal/catch_jsonwriter.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_lazy_expr.cpp (renamed from src/catch2/internal/catch_lazy_expr.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_lazy_expr.hpp (renamed from src/catch2/internal/catch_lazy_expr.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_leak_detector.cpp (renamed from src/catch2/internal/catch_leak_detector.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_leak_detector.hpp (renamed from src/catch2/internal/catch_leak_detector.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_list.cpp (renamed from src/catch2/internal/catch_list.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_list.hpp (renamed from src/catch2/internal/catch_list.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_logical_traits.hpp (renamed from src/catch2/internal/catch_logical_traits.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_main.cpp (renamed from src/catch2/internal/catch_main.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_message_info.cpp (renamed from src/catch2/internal/catch_message_info.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_message_info.hpp (renamed from src/catch2/internal/catch_message_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_meta.hpp (renamed from src/catch2/internal/catch_meta.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_move_and_forward.hpp (renamed from src/catch2/internal/catch_move_and_forward.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_noncopyable.hpp (renamed from src/catch2/internal/catch_noncopyable.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_optional.hpp (renamed from src/catch2/internal/catch_optional.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_output_redirect.cpp (renamed from src/catch2/internal/catch_output_redirect.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_output_redirect.hpp (renamed from src/catch2/internal/catch_output_redirect.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_parse_numbers.cpp (renamed from src/catch2/internal/catch_parse_numbers.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_parse_numbers.hpp (renamed from src/catch2/internal/catch_parse_numbers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_platform.hpp (renamed from src/catch2/internal/catch_platform.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_polyfills.cpp (renamed from src/catch2/internal/catch_polyfills.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_polyfills.hpp (renamed from src/catch2/internal/catch_polyfills.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_preprocessor.hpp (renamed from src/catch2/internal/catch_preprocessor.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_preprocessor_internal_stringify.hpp (renamed from src/catch2/internal/catch_preprocessor_internal_stringify.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_preprocessor_remove_parens.hpp (renamed from src/catch2/internal/catch_preprocessor_remove_parens.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_floating_point_helpers.hpp (renamed from src/catch2/internal/catch_random_floating_point_helpers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_integer_helpers.hpp (renamed from src/catch2/internal/catch_random_integer_helpers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_number_generator.cpp (renamed from src/catch2/internal/catch_random_number_generator.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_number_generator.hpp (renamed from src/catch2/internal/catch_random_number_generator.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_seed_generation.cpp (renamed from src/catch2/internal/catch_random_seed_generation.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_random_seed_generation.hpp (renamed from src/catch2/internal/catch_random_seed_generation.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reporter_registry.cpp (renamed from src/catch2/internal/catch_reporter_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reporter_registry.hpp (renamed from src/catch2/internal/catch_reporter_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.cpp (renamed from src/catch2/internal/catch_reporter_spec_parser.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.hpp (renamed from src/catch2/internal/catch_reporter_spec_parser.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_result_type.hpp (renamed from src/catch2/internal/catch_result_type.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reusable_string_stream.cpp (renamed from src/catch2/internal/catch_reusable_string_stream.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_reusable_string_stream.hpp (renamed from src/catch2/internal/catch_reusable_string_stream.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_run_context.cpp (renamed from src/catch2/internal/catch_run_context.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_run_context.hpp (renamed from src/catch2/internal/catch_run_context.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_section.cpp (renamed from src/catch2/internal/catch_section.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_section.hpp (renamed from src/catch2/internal/catch_section.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_sharding.hpp (renamed from src/catch2/internal/catch_sharding.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_singletons.cpp (renamed from src/catch2/internal/catch_singletons.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_singletons.hpp (renamed from src/catch2/internal/catch_singletons.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_source_line_info.cpp (renamed from src/catch2/internal/catch_source_line_info.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_source_line_info.hpp (renamed from src/catch2/internal/catch_source_line_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_startup_exception_registry.cpp (renamed from src/catch2/internal/catch_startup_exception_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_startup_exception_registry.hpp (renamed from src/catch2/internal/catch_startup_exception_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_stdstreams.cpp (renamed from src/catch2/internal/catch_stdstreams.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_stdstreams.hpp (renamed from src/catch2/internal/catch_stdstreams.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_stream_end_stop.hpp (renamed from src/catch2/internal/catch_stream_end_stop.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_string_manip.cpp (renamed from src/catch2/internal/catch_string_manip.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_string_manip.hpp (renamed from src/catch2/internal/catch_string_manip.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_stringref.cpp (renamed from src/catch2/internal/catch_stringref.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_stringref.hpp (renamed from src/catch2/internal/catch_stringref.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_tag_alias_registry.cpp (renamed from src/catch2/internal/catch_tag_alias_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_tag_alias_registry.hpp (renamed from src/catch2/internal/catch_tag_alias_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_template_test_registry.hpp (renamed from src/catch2/internal/catch_template_test_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.cpp (renamed from src/catch2/internal/catch_test_case_info_hasher.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.hpp (renamed from src/catch2/internal/catch_test_case_info_hasher.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.cpp (renamed from src/catch2/internal/catch_test_case_registry_impl.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.hpp (renamed from src/catch2/internal/catch_test_case_registry_impl.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_tracker.cpp (renamed from src/catch2/internal/catch_test_case_tracker.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_case_tracker.hpp (renamed from src/catch2/internal/catch_test_case_tracker.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_failure_exception.cpp (renamed from src/catch2/internal/catch_test_failure_exception.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_failure_exception.hpp (renamed from src/catch2/internal/catch_test_failure_exception.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_macro_impl.hpp (renamed from src/catch2/internal/catch_test_macro_impl.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_registry.cpp (renamed from src/catch2/internal/catch_test_registry.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_registry.hpp (renamed from src/catch2/internal/catch_test_registry.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_run_info.hpp (renamed from src/catch2/internal/catch_test_run_info.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_spec_parser.cpp (renamed from src/catch2/internal/catch_test_spec_parser.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_test_spec_parser.hpp (renamed from src/catch2/internal/catch_test_spec_parser.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_textflow.cpp (renamed from src/catch2/internal/catch_textflow.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_textflow.hpp (renamed from src/catch2/internal/catch_textflow.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_to_string.hpp (renamed from src/catch2/internal/catch_to_string.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.cpp (renamed from src/catch2/internal/catch_uncaught_exceptions.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.hpp (renamed from src/catch2/internal/catch_uncaught_exceptions.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_uniform_floating_point_distribution.hpp (renamed from src/catch2/internal/catch_uniform_floating_point_distribution.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_uniform_integer_distribution.hpp (renamed from src/catch2/internal/catch_uniform_integer_distribution.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_unique_name.hpp (renamed from src/catch2/internal/catch_unique_name.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_unique_ptr.hpp (renamed from src/catch2/internal/catch_unique_ptr.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_void_type.hpp (renamed from src/catch2/internal/catch_void_type.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_wildcard_pattern.cpp (renamed from src/catch2/internal/catch_wildcard_pattern.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_wildcard_pattern.hpp (renamed from src/catch2/internal/catch_wildcard_pattern.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_windows_h_proxy.hpp (renamed from src/catch2/internal/catch_windows_h_proxy.hpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_xmlwriter.cpp (renamed from src/catch2/internal/catch_xmlwriter.cpp)0
-rw-r--r--contrib/catch2/src/catch2/internal/catch_xmlwriter.hpp (renamed from src/catch2/internal/catch_xmlwriter.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers.cpp (renamed from src/catch2/matchers/catch_matchers.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers.hpp (renamed from src/catch2/matchers/catch_matchers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_all.hpp (renamed from src/catch2/matchers/catch_matchers_all.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.cpp (renamed from src/catch2/matchers/catch_matchers_container_properties.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.hpp (renamed from src/catch2/matchers/catch_matchers_container_properties.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_contains.hpp (renamed from src/catch2/matchers/catch_matchers_contains.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_exception.cpp (renamed from src/catch2/matchers/catch_matchers_exception.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_exception.hpp (renamed from src/catch2/matchers/catch_matchers_exception.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.cpp (renamed from src/catch2/matchers/catch_matchers_floating_point.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.hpp (renamed from src/catch2/matchers/catch_matchers_floating_point.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_predicate.cpp (renamed from src/catch2/matchers/catch_matchers_predicate.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_predicate.hpp (renamed from src/catch2/matchers/catch_matchers_predicate.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.cpp (renamed from src/catch2/matchers/catch_matchers_quantifiers.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.hpp (renamed from src/catch2/matchers/catch_matchers_quantifiers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_range_equals.hpp (renamed from src/catch2/matchers/catch_matchers_range_equals.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_string.cpp (renamed from src/catch2/matchers/catch_matchers_string.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_string.hpp (renamed from src/catch2/matchers/catch_matchers_string.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_templated.cpp (renamed from src/catch2/matchers/catch_matchers_templated.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_templated.hpp (renamed from src/catch2/matchers/catch_matchers_templated.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/catch_matchers_vector.hpp (renamed from src/catch2/matchers/catch_matchers_vector.hpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.cpp (renamed from src/catch2/matchers/internal/catch_matchers_impl.cpp)0
-rw-r--r--contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.hpp (renamed from src/catch2/matchers/internal/catch_matchers_impl.hpp)0
-rw-r--r--contrib/catch2/src/catch2/meson.build (renamed from src/catch2/meson.build)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_automake.cpp (renamed from src/catch2/reporters/catch_reporter_automake.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_automake.hpp (renamed from src/catch2/reporters/catch_reporter_automake.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_common_base.cpp (renamed from src/catch2/reporters/catch_reporter_common_base.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_common_base.hpp (renamed from src/catch2/reporters/catch_reporter_common_base.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_compact.cpp (renamed from src/catch2/reporters/catch_reporter_compact.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_compact.hpp (renamed from src/catch2/reporters/catch_reporter_compact.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_console.cpp (renamed from src/catch2/reporters/catch_reporter_console.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_console.hpp (renamed from src/catch2/reporters/catch_reporter_console.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.cpp (renamed from src/catch2/reporters/catch_reporter_cumulative_base.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.hpp (renamed from src/catch2/reporters/catch_reporter_cumulative_base.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.cpp (renamed from src/catch2/reporters/catch_reporter_event_listener.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.hpp (renamed from src/catch2/reporters/catch_reporter_event_listener.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_helpers.cpp (renamed from src/catch2/reporters/catch_reporter_helpers.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_helpers.hpp (renamed from src/catch2/reporters/catch_reporter_helpers.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_json.cpp (renamed from src/catch2/reporters/catch_reporter_json.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_json.hpp (renamed from src/catch2/reporters/catch_reporter_json.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_junit.cpp (renamed from src/catch2/reporters/catch_reporter_junit.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_junit.hpp (renamed from src/catch2/reporters/catch_reporter_junit.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_multi.cpp (renamed from src/catch2/reporters/catch_reporter_multi.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_multi.hpp (renamed from src/catch2/reporters/catch_reporter_multi.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_registrars.cpp (renamed from src/catch2/reporters/catch_reporter_registrars.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_registrars.hpp (renamed from src/catch2/reporters/catch_reporter_registrars.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.cpp (renamed from src/catch2/reporters/catch_reporter_sonarqube.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.hpp (renamed from src/catch2/reporters/catch_reporter_sonarqube.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.cpp (renamed from src/catch2/reporters/catch_reporter_streaming_base.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.hpp (renamed from src/catch2/reporters/catch_reporter_streaming_base.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_tap.cpp (renamed from src/catch2/reporters/catch_reporter_tap.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_tap.hpp (renamed from src/catch2/reporters/catch_reporter_tap.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.cpp (renamed from src/catch2/reporters/catch_reporter_teamcity.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.hpp (renamed from src/catch2/reporters/catch_reporter_teamcity.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_xml.cpp (renamed from src/catch2/reporters/catch_reporter_xml.cpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporter_xml.hpp (renamed from src/catch2/reporters/catch_reporter_xml.hpp)0
-rw-r--r--contrib/catch2/src/catch2/reporters/catch_reporters_all.hpp (renamed from src/catch2/reporters/catch_reporters_all.hpp)0
-rw-r--r--contrib/catch2/tests/BUILD.bazel (renamed from tests/BUILD.bazel)0
-rw-r--r--contrib/catch2/tests/CMakeLists.txt (renamed from tests/CMakeLists.txt)0
-rw-r--r--contrib/catch2/tests/ExtraTests/CMakeLists.txt (renamed from tests/ExtraTests/CMakeLists.txt)0
-rw-r--r--contrib/catch2/tests/ExtraTests/ToDo.txt (renamed from tests/ExtraTests/ToDo.txt)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X01-PrefixedMacros.cpp (renamed from tests/ExtraTests/X01-PrefixedMacros.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X02-DisabledMacros.cpp (renamed from tests/ExtraTests/X02-DisabledMacros.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp (renamed from tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp (renamed from tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X05-DeferredStaticChecks.cpp (renamed from tests/ExtraTests/X05-DeferredStaticChecks.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X10-FallbackStringifier.cpp (renamed from tests/ExtraTests/X10-FallbackStringifier.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X11-DisableStringification.cpp (renamed from tests/ExtraTests/X11-DisableStringification.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X12-CustomDebugBreakMacro.cpp (renamed from tests/ExtraTests/X12-CustomDebugBreakMacro.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp (renamed from tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X21-PartialTestCaseEvents.cpp (renamed from tests/ExtraTests/X21-PartialTestCaseEvents.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp (renamed from tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X23-CasingInReporterNames.cpp (renamed from tests/ExtraTests/X23-CasingInReporterNames.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp (renamed from tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp (renamed from tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp (renamed from tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp (renamed from tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp (renamed from tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X29-CustomArgumentsForReporters.cpp (renamed from tests/ExtraTests/X29-CustomArgumentsForReporters.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X30-BazelReporter.cpp (renamed from tests/ExtraTests/X30-BazelReporter.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X31-DuplicatedTestCases.cpp (renamed from tests/ExtraTests/X31-DuplicatedTestCases.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp (renamed from tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp (renamed from tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp (renamed from tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X35-DuplicatedReporterNames.cpp (renamed from tests/ExtraTests/X35-DuplicatedReporterNames.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp (renamed from tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X90-WindowsHeaderInclusion.cpp (renamed from tests/ExtraTests/X90-WindowsHeaderInclusion.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X91-AmalgamatedCatch.cpp (renamed from tests/ExtraTests/X91-AmalgamatedCatch.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X92-NoTests.cpp (renamed from tests/ExtraTests/X92-NoTests.cpp)0
-rw-r--r--contrib/catch2/tests/ExtraTests/X93-AllSkipped.cpp (renamed from tests/ExtraTests/X93-AllSkipped.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/automake.std.approved.txt (renamed from tests/SelfTest/Baselines/automake.std.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/automake.sw.approved.txt (renamed from tests/SelfTest/Baselines/automake.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/automake.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/automake.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/compact.sw.approved.txt (renamed from tests/SelfTest/Baselines/compact.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/compact.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/compact.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/console.std.approved.txt (renamed from tests/SelfTest/Baselines/console.std.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/console.sw.approved.txt (renamed from tests/SelfTest/Baselines/console.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/console.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/console.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/console.swa4.approved.txt (renamed from tests/SelfTest/Baselines/console.swa4.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/default.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/default.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/junit.sw.approved.txt (renamed from tests/SelfTest/Baselines/junit.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/junit.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/junit.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.approved.txt (renamed from tests/SelfTest/Baselines/sonarqube.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/tap.sw.approved.txt (renamed from tests/SelfTest/Baselines/tap.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/tap.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/tap.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.approved.txt (renamed from tests/SelfTest/Baselines/teamcity.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/xml.sw.approved.txt (renamed from tests/SelfTest/Baselines/xml.sw.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/Baselines/xml.sw.multi.approved.txt (renamed from tests/SelfTest/Baselines/xml.sw.multi.approved.txt)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Clara.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Details.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Details.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Integer.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Json.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Json.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Parse.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Stream.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/String.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/String.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Tag.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/ToString.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Traits.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp (renamed from tests/SelfTest/IntrospectiveTests/Xml.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/Misc/invalid-test-names.input (renamed from tests/SelfTest/Misc/invalid-test-names.input)0
-rw-r--r--contrib/catch2/tests/SelfTest/Misc/plain-old-tests.input (renamed from tests/SelfTest/Misc/plain-old-tests.input)0
-rw-r--r--contrib/catch2/tests/SelfTest/Misc/special-characters-in-file.input (renamed from tests/SelfTest/Misc/special-characters-in-file.input)0
-rw-r--r--contrib/catch2/tests/SelfTest/TestRegistrations.cpp (renamed from tests/SelfTest/TestRegistrations.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/TimingTests/Sleep.tests.cpp (renamed from tests/SelfTest/TimingTests/Sleep.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Approx.tests.cpp (renamed from tests/SelfTest/UsageTests/Approx.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/BDD.tests.cpp (renamed from tests/SelfTest/UsageTests/BDD.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Benchmark.tests.cpp (renamed from tests/SelfTest/UsageTests/Benchmark.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Class.tests.cpp (renamed from tests/SelfTest/UsageTests/Class.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Compilation.tests.cpp (renamed from tests/SelfTest/UsageTests/Compilation.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Condition.tests.cpp (renamed from tests/SelfTest/UsageTests/Condition.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Decomposition.tests.cpp (renamed from tests/SelfTest/UsageTests/Decomposition.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/EnumToString.tests.cpp (renamed from tests/SelfTest/UsageTests/EnumToString.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Exception.tests.cpp (renamed from tests/SelfTest/UsageTests/Exception.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Generators.tests.cpp (renamed from tests/SelfTest/UsageTests/Generators.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Matchers.tests.cpp (renamed from tests/SelfTest/UsageTests/Matchers.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp (renamed from tests/SelfTest/UsageTests/MatchersRanges.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Message.tests.cpp (renamed from tests/SelfTest/UsageTests/Message.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Misc.tests.cpp (renamed from tests/SelfTest/UsageTests/Misc.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Skip.tests.cpp (renamed from tests/SelfTest/UsageTests/Skip.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringByte.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringByte.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringChrono.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringOptional.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringPair.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringPair.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringTuple.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringVariant.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringVector.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringVector.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp (renamed from tests/SelfTest/UsageTests/ToStringWhich.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/Tricky.tests.cpp (renamed from tests/SelfTest/UsageTests/Tricky.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp (renamed from tests/SelfTest/UsageTests/VariadicMacros.tests.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/helpers/parse_test_spec.cpp (renamed from tests/SelfTest/helpers/parse_test_spec.cpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/helpers/parse_test_spec.hpp (renamed from tests/SelfTest/helpers/parse_test_spec.hpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/helpers/range_test_helpers.hpp (renamed from tests/SelfTest/helpers/range_test_helpers.hpp)0
-rw-r--r--contrib/catch2/tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp (renamed from tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp)0
-rw-r--r--contrib/catch2/tests/TestScripts/ConfigureTestsCommon.py (renamed from tests/TestScripts/ConfigureTestsCommon.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/DiscoverTests/CMakeLists.txt (renamed from tests/TestScripts/DiscoverTests/CMakeLists.txt)0
-rw-r--r--contrib/catch2/tests/TestScripts/DiscoverTests/VerifyRegistration.py (renamed from tests/TestScripts/DiscoverTests/VerifyRegistration.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/DiscoverTests/register-tests.cpp (renamed from tests/TestScripts/DiscoverTests/register-tests.cpp)0
-rw-r--r--contrib/catch2/tests/TestScripts/testBazelReporter.py (renamed from tests/TestScripts/testBazelReporter.py)0
-rwxr-xr-xcontrib/catch2/tests/TestScripts/testBazelSharding.py (renamed from tests/TestScripts/testBazelSharding.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/testConfigureDefaultReporter.py (renamed from tests/TestScripts/testConfigureDefaultReporter.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/testConfigureDisable.py (renamed from tests/TestScripts/testConfigureDisable.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/testConfigureDisableStringification.py (renamed from tests/TestScripts/testConfigureDisableStringification.py)0
-rw-r--r--contrib/catch2/tests/TestScripts/testConfigureExperimentalRedirect.py (renamed from tests/TestScripts/testConfigureExperimentalRedirect.py)0
-rwxr-xr-xcontrib/catch2/tests/TestScripts/testPartialTestCaseEvent.py (renamed from tests/TestScripts/testPartialTestCaseEvent.py)0
-rwxr-xr-xcontrib/catch2/tests/TestScripts/testRandomOrder.py (renamed from tests/TestScripts/testRandomOrder.py)0
-rwxr-xr-xcontrib/catch2/tests/TestScripts/testSharding.py (renamed from tests/TestScripts/testSharding.py)0
-rw-r--r--contrib/catch2/tests/meson.build (renamed from tests/meson.build)0
-rw-r--r--contrib/catch2/third_party/clara.hpp (renamed from third_party/clara.hpp)0
-rw-r--r--contrib/catch2/tools/misc/CMakeLists.txt (renamed from tools/misc/CMakeLists.txt)0
-rw-r--r--contrib/catch2/tools/misc/appveyorBuildConfigurationScript.bat (renamed from tools/misc/appveyorBuildConfigurationScript.bat)0
-rw-r--r--contrib/catch2/tools/misc/appveyorMergeCoverageScript.py (renamed from tools/misc/appveyorMergeCoverageScript.py)0
-rw-r--r--contrib/catch2/tools/misc/appveyorTestRunScript.bat (renamed from tools/misc/appveyorTestRunScript.bat)0
-rw-r--r--contrib/catch2/tools/misc/coverage-helper.cpp (renamed from tools/misc/coverage-helper.cpp)0
-rw-r--r--contrib/catch2/tools/misc/installOpenCppCoverage.ps1 (renamed from tools/misc/installOpenCppCoverage.ps1)0
-rwxr-xr-xcontrib/catch2/tools/scripts/approvalTests.py (renamed from tools/scripts/approvalTests.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/approve.py (renamed from tools/scripts/approve.py)0
-rw-r--r--contrib/catch2/tools/scripts/buildAndTest.cmd (renamed from tools/scripts/buildAndTest.cmd)0
-rwxr-xr-xcontrib/catch2/tools/scripts/buildAndTest.sh (renamed from tools/scripts/buildAndTest.sh)0
-rwxr-xr-xcontrib/catch2/tools/scripts/checkConvenienceHeaders.py (renamed from tools/scripts/checkConvenienceHeaders.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/checkDuplicateFilenames.py (renamed from tools/scripts/checkDuplicateFilenames.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/checkLicense.py (renamed from tools/scripts/checkLicense.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/developBuild.py (renamed from tools/scripts/developBuild.py)0
-rw-r--r--contrib/catch2/tools/scripts/extractFeaturesFromReleaseNotes.py (renamed from tools/scripts/extractFeaturesFromReleaseNotes.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/fixWhitespace.py (renamed from tools/scripts/fixWhitespace.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/generateAmalgamatedFiles.py (renamed from tools/scripts/generateAmalgamatedFiles.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/majorRelease.py (renamed from tools/scripts/majorRelease.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/minorRelease.py (renamed from tools/scripts/minorRelease.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/patchRelease.py (renamed from tools/scripts/patchRelease.py)0
-rw-r--r--contrib/catch2/tools/scripts/releaseCommon.py (renamed from tools/scripts/releaseCommon.py)0
-rw-r--r--contrib/catch2/tools/scripts/scriptCommon.py (renamed from tools/scripts/scriptCommon.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/updateDocumentSnippets.py (renamed from tools/scripts/updateDocumentSnippets.py)0
-rwxr-xr-xcontrib/catch2/tools/scripts/updateDocumentToC.py (renamed from tools/scripts/updateDocumentToC.py)0
-rw-r--r--nihil.cli/CMakeLists.txt40
-rw-r--r--nihil.cli/command.cc52
-rw-r--r--nihil.cli/command.ccm48
-rw-r--r--nihil.cli/command_node.cc49
-rw-r--r--nihil.cli/command_node.ccm35
-rw-r--r--nihil.cli/command_tree.cc179
-rw-r--r--nihil.cli/command_tree.ccm107
-rw-r--r--nihil.cli/dispatch_command.cc82
-rw-r--r--nihil.cli/dispatch_command.ccm31
-rw-r--r--nihil.cli/nihil.cli.ccm14
-rw-r--r--nihil.cli/registry.cc57
-rw-r--r--nihil.cli/registry.ccm28
-rw-r--r--nihil.cli/test.cc104
-rw-r--r--nihil.cli/usage_error.ccm22
-rw-r--r--nihil.config/CMakeLists.txt31
-rw-r--r--nihil.config/nihil.config.ccm13
-rw-r--r--nihil.config/option.cc97
-rw-r--r--nihil.config/option.ccm105
-rw-r--r--nihil.config/read.cc50
-rw-r--r--nihil.config/read.ccm22
-rw-r--r--nihil.config/store.cc95
-rw-r--r--nihil.config/store.ccm75
-rw-r--r--nihil.config/string.cc62
-rw-r--r--nihil.config/string.ccm56
-rw-r--r--nihil.config/tests/CMakeLists.txt15
-rw-r--r--nihil.config/tests/string.cc36
-rw-r--r--nihil.config/write.cc41
-rw-r--r--nihil.config/write.ccm22
-rw-r--r--nihil.core/CMakeLists.txt12
-rw-r--r--nihil.core/errc.cc51
-rw-r--r--nihil.core/errc.ccm44
-rw-r--r--nihil.core/nihil.core.ccm9
-rw-r--r--nihil.core/nihil.hh26
-rw-r--r--nihil.error/CMakeLists.txt29
-rw-r--r--nihil.error/error.cc160
-rw-r--r--nihil.error/error.ccm199
-rw-r--r--nihil.error/test.cc169
-rw-r--r--nihil.flagset/CMakeLists.txt23
-rw-r--r--nihil.flagset/flagset.ccm228
-rw-r--r--nihil.flagset/test.cc149
-rw-r--r--nihil.generator/CMakeLists.txt29
-rw-r--r--nihil.generator/elements_of.ccm69
-rw-r--r--nihil.generator/generator.ccm507
-rw-r--r--nihil.generator/manual_lifetime.ccm117
-rw-r--r--nihil.generator/nihil.generator.ccm32
-rw-r--r--nihil.generator/promise_base_alloc.ccm94
-rw-r--r--nihil.generator/test.cc56
-rw-r--r--nihil.generator/util.ccm37
-rw-r--r--nihil.guard/CMakeLists.txt23
-rw-r--r--nihil.guard/guard.ccm53
-rw-r--r--nihil.guard/test.cc20
-rw-r--r--nihil.match/CMakeLists.txt23
-rw-r--r--nihil.match/match.ccm23
-rw-r--r--nihil.match/test.cc34
-rw-r--r--nihil.monad/CMakeLists.txt24
-rw-r--r--nihil.monad/monad.ccm289
-rw-r--r--nihil.monad/test.cc69
-rw-r--r--nihil.posix/CMakeLists.txt61
-rw-r--r--nihil.posix/argv.cc65
-rw-r--r--nihil.posix/argv.ccm78
-rw-r--r--nihil.posix/ensure_dir.cc30
-rw-r--r--nihil.posix/ensure_dir.ccm23
-rw-r--r--nihil.posix/exec.cc71
-rw-r--r--nihil.posix/exec.ccm105
-rw-r--r--nihil.posix/executor.ccm27
-rw-r--r--nihil.posix/execv.cc43
-rw-r--r--nihil.posix/execv.ccm47
-rw-r--r--nihil.posix/fd.cc220
-rw-r--r--nihil.posix/fd.ccm157
-rw-r--r--nihil.posix/fexecv.ccm53
-rw-r--r--nihil.posix/find_in_path.cc52
-rw-r--r--nihil.posix/find_in_path.ccm24
-rw-r--r--nihil.posix/getenv.cc54
-rw-r--r--nihil.posix/getenv.ccm23
-rw-r--r--nihil.posix/open.cc31
-rw-r--r--nihil.posix/open.ccm24
-rw-r--r--nihil.posix/open_in_path.cc51
-rw-r--r--nihil.posix/open_in_path.ccm23
-rw-r--r--nihil.posix/posix.ccm35
-rw-r--r--nihil.posix/process.cc102
-rw-r--r--nihil.posix/process.ccm91
-rw-r--r--nihil.posix/read_file.ccm49
-rw-r--r--nihil.posix/rename.cc34
-rw-r--r--nihil.posix/rename.ccm23
-rw-r--r--nihil.posix/spawn.ccm246
-rw-r--r--nihil.posix/tempfile.cc128
-rw-r--r--nihil.posix/tempfile.ccm87
-rw-r--r--nihil.posix/test.fd.cc199
-rw-r--r--nihil.posix/test.getenv.cc50
-rw-r--r--nihil.posix/test.spawn.cc117
-rw-r--r--nihil.posix/test.tempfile.cc90
-rw-r--r--nihil.posix/write_file.ccm82
-rw-r--r--nihil.ucl/CMakeLists.txt46
-rw-r--r--nihil.ucl/array.ccm468
-rw-r--r--nihil.ucl/boolean.cc106
-rw-r--r--nihil.ucl/boolean.ccm91
-rw-r--r--nihil.ucl/emit.cc21
-rw-r--r--nihil.ucl/emit.ccm209
-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.ccm115
-rw-r--r--nihil.ucl/map.ccm293
-rw-r--r--nihil.ucl/nihil.ucl.ccm21
-rw-r--r--nihil.ucl/object.cc114
-rw-r--r--nihil.ucl/object.ccm88
-rw-r--r--nihil.ucl/object_cast.ccm89
-rw-r--r--nihil.ucl/parser.cc102
-rw-r--r--nihil.ucl/parser.ccm160
-rw-r--r--nihil.ucl/real.cc104
-rw-r--r--nihil.ucl/real.ccm112
-rw-r--r--nihil.ucl/string.cc187
-rw-r--r--nihil.ucl/string.ccm229
-rw-r--r--nihil.ucl/tests/CMakeLists.txt22
-rw-r--r--nihil.ucl/tests/array.cc478
-rw-r--r--nihil.ucl/tests/boolean.cc224
-rw-r--r--nihil.ucl/tests/emit.cc93
-rw-r--r--nihil.ucl/tests/integer.cc247
-rw-r--r--nihil.ucl/tests/map.cc192
-rw-r--r--nihil.ucl/tests/object.cc44
-rw-r--r--nihil.ucl/tests/parse.cc55
-rw-r--r--nihil.ucl/tests/real.cc248
-rw-r--r--nihil.ucl/tests/string.cc415
-rw-r--r--nihil.ucl/type.cc62
-rw-r--r--nihil.ucl/type.ccm58
-rw-r--r--nihil.util/CMakeLists.txt38
-rw-r--r--nihil.util/capture_stream.ccm62
-rw-r--r--nihil.util/ctype.ccm87
-rw-r--r--nihil.util/next_word.ccm49
-rw-r--r--nihil.util/nihil.util.ccm14
-rw-r--r--nihil.util/parse_size.ccm107
-rw-r--r--nihil.util/skipws.ccm40
-rw-r--r--nihil.util/tabulate.ccm312
-rw-r--r--nihil.util/test_capture_stream.cc44
-rw-r--r--nihil.util/test_ctype.cc373
-rw-r--r--nihil.util/test_next_word.cc65
-rw-r--r--nihil.util/test_parse_size.cc169
-rw-r--r--nihil.util/test_skipws.cc45
-rw-r--r--nihil.util/test_tabulate.cc75
-rw-r--r--nihil.uuid/CMakeLists.txt23
-rw-r--r--nihil.uuid/test.cc1001
-rw-r--r--nihil.uuid/uuid.ccm842
704 files changed, 15362 insertions, 266 deletions
diff --git a/.clang-format b/.clang-format
index 9efb854..c2dc3e0 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,45 +1,6 @@
----
-Language: Cpp
-Standard: c++14
-
-# Note that we cannot use IncludeIsMainRegex functionality, because it
-# does not support includes in angle brackets (<>)
-SortIncludes: true
-IncludeBlocks: Regroup
-IncludeCategories:
- - Regex: <catch2/.*\.hpp>
- Priority: 1
- - Regex: <.*/.*\.hpp>
- Priority: 2
- - Regex: <.*>
- Priority: 3
-
-AllowShortBlocksOnASingleLine: Always
-AllowShortEnumsOnASingleLine: false
-AllowShortFunctionsOnASingleLine: All
-AllowShortIfStatementsOnASingleLine: WithoutElse
-AllowShortLambdasOnASingleLine: Inline
-
-AccessModifierOffset: "-4"
-AlignEscapedNewlines: Left
-AllowAllConstructorInitializersOnNextLine: "true"
-BinPackArguments: "false"
-BinPackParameters: "false"
-BreakConstructorInitializers: AfterColon
-ConstructorInitializerAllOnOneLineOrOnePerLine: "true"
-DerivePointerAlignment: "false"
-FixNamespaceComments: "true"
-IndentCaseLabels: "false"
+TabWidth: 8
+IndentWidth: 8
+PPIndentWidth: -1
+UseTab: AlignWithSpaces
IndentPPDirectives: AfterHash
-IndentWidth: "4"
-NamespaceIndentation: All
-PointerAlignment: Left
-SpaceBeforeCtorInitializerColon: "false"
-SpaceInEmptyParentheses: "false"
-SpacesInParentheses: "true"
-TabWidth: "4"
-UseTab: Never
-AlwaysBreakTemplateDeclarations: Yes
-SpaceAfterTemplateKeyword: true
-SortUsingDeclarations: true
-ReflowComments: true
+ColumnLimit: 100 \ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dbf9f40..a8dc4b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,40 +1,4 @@
-*.build
-!meson.build
-*.pbxuser
-*.mode1v3
-*.ncb
-*.suo
-Debug
-Release
-*.user
-*.xcuserstate
-.DS_Store
-xcuserdata
-CatchSelfTest.xcscheme
-Breakpoints.xcbkptlist
-UpgradeLog.XML
-Resources/DWARF
-projects/Generated
-*.pyc
-DerivedData
-*.xccheckout
-Build
-.idea
-.vs
-.vscode
-cmake-build-*
-benchmark-dir
-.conan/test_package/build
-**/CMakeUserPresets.json
-bazel-*
-MODULE.bazel.lock
-build-fuzzers
-debug-build
-.vscode
-msvc-sln*
-# Currently we use Doxygen for dep graphs and the full docs are only slowly
-# being filled in, so we definitely do not want git to deal with the docs.
-docs/doxygen
-*.cache
-compile_commands.json
-**/*.unapproved.txt
+/build*
+/dist
+*.sw?
+/.idea
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d8226b..8e8b131 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,198 +1,66 @@
-cmake_minimum_required(VERSION 3.16)
+# This source code is released into the public domain.
-# detect if Catch is being bundled,
-# disable testsuite in that case
-if(NOT DEFINED PROJECT_NAME)
- set(NOT_SUBPROJECT ON)
-else()
- set(NOT_SUBPROJECT OFF)
-endif()
-
-set_property(GLOBAL PROPERTY USE_FOLDERS ON)
-
-option(CATCH_INSTALL_DOCS "Install documentation alongside library" ON)
-option(CATCH_INSTALL_EXTRAS "Install extras (CMake scripts, debugger helpers) alongside library" ON)
-option(CATCH_DEVELOPMENT_BUILD "Build tests, enable warnings, enable Werror, etc" OFF)
-option(CATCH_ENABLE_REPRODUCIBLE_BUILD "Add compiler flags for improving build reproducibility" ON)
-
-include(CMakeDependentOption)
-cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_ENABLE_CONFIGURE_TESTS "Enable CMake configuration tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-cmake_dependent_option(CATCH_ENABLE_CMAKE_HELPER_TESTS "Enable CMake helper tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
-
-
-# Catch2's build breaks if done in-tree. You probably should not build
-# things in tree anyway, but we can allow projects that include Catch2
-# as a subproject to build in-tree as long as it is not in our tree.
-if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
- message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
-endif()
-
-project(Catch2
- VERSION 3.8.1 # CML version placeholder, don't delete
- LANGUAGES CXX
- HOMEPAGE_URL "https://github.com/catchorg/Catch2"
- DESCRIPTION "A modern, C++-native, unit test framework."
-)
-
-# Provide path for scripts. We first add path to the scripts we don't use,
-# but projects including us might, and set the path up to parent scope.
-# Then we also add path that we use to configure the project, but is of
-# no use to top level projects.
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/extras")
-if (NOT NOT_SUBPROJECT)
- set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE)
-endif()
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
-
-include(GNUInstallDirs)
-include(CMakePackageConfigHelpers)
-include(CatchConfigOptions)
-if(CATCH_DEVELOPMENT_BUILD)
- include(CTest)
-endif()
+cmake_minimum_required(VERSION 3.28)
-# This variable is used in some subdirectories, so we need it here, rather
-# than later in the install block
-set(CATCH_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Catch2")
+project(nihil)
-# We have some Windows builds that test `wmain` entry point,
-# and we need this change to be present in all binaries that
-# are built during these tests, so this is required here, before
-# the subdirectories are added.
-if(CATCH_TEST_USE_WMAIN)
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ENTRY:wmainCRTStartup")
-endif()
+option(NIHIL_CONFIG "Build the nihil.config library" ON)
+option(NIHIL_UCL "Build the nihil.ucl library" ON)
+option(NIHIL_TESTS "Build nihil's unit tests" ON)
+option(NIHIL_TIDY "Run clang-tidy during build" ON)
+set(CMAKE_CXX_STANDARD 26)
-# Basic paths
-set(CATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-set(SOURCES_DIR ${CATCH_DIR}/src/catch2)
-set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
+find_package(PkgConfig REQUIRED)
-# We need to bring-in the variables defined there to this scope
-add_subdirectory(src)
+# clang-tidy support
+find_program(CLANG_TIDY clang-tidy)
-# Build tests only if requested
-if (BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
- find_package(PythonInterp 3 REQUIRED)
- if (NOT PYTHONINTERP_FOUND)
- message(FATAL_ERROR "Python not found, but required for tests")
- endif()
- set(CMAKE_FOLDER "tests")
- add_subdirectory(tests)
-endif()
+if(NOT (CLANG_TIDY STREQUAL ""))
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-if(CATCH_BUILD_EXAMPLES)
- set(CMAKE_FOLDER "Examples")
- add_subdirectory(examples)
-endif()
+ file(GLOB_RECURSE NIHIL_SOURCES "*.cc" "*.ccm")
+ list(FILTER NIHIL_SOURCES EXCLUDE REGEX ${CMAKE_CURRENT_BINARY_DIR})
-if(CATCH_BUILD_EXTRA_TESTS)
- set(CMAKE_FOLDER "tests/ExtraTests")
- add_subdirectory(tests/ExtraTests)
+ add_custom_target(tidy COMMAND
+ ${CLANG_TIDY} -config-file=${CMAKE_CURRENT_SOURCE_DIR}/clang-tidy.conf
+ -p=${CMAKE_CURRENT_BINARY_DIR}
+ ${NIHIL_SOURCES})
+
+ if(NIHIL_TIDY)
+ set(CMAKE_CXX_CLANG_TIDY
+ ${CLANG_TIDY};
+ -config-file=${CMAKE_CURRENT_SOURCE_DIR}/clang-tidy.conf;
+ )
+ endif()
endif()
-if(CATCH_BUILD_FUZZERS)
- set(CMAKE_FOLDER "fuzzing")
- add_subdirectory(fuzzing)
+add_compile_options(-W)
+add_compile_options(-Wall)
+add_compile_options(-Wextra)
+add_compile_options(-Werror)
+add_compile_options(-Wpedantic)
+
+add_subdirectory(nihil.cli)
+add_subdirectory(nihil.core)
+add_subdirectory(nihil.error)
+add_subdirectory(nihil.flagset)
+add_subdirectory(nihil.generator)
+add_subdirectory(nihil.guard)
+add_subdirectory(nihil.match)
+add_subdirectory(nihil.monad)
+add_subdirectory(nihil.posix)
+add_subdirectory(nihil.util)
+add_subdirectory(nihil.uuid)
+
+if(NIHIL_UCL)
+ add_subdirectory(nihil.ucl)
endif()
-if (CATCH_DEVELOPMENT_BUILD)
- add_warnings_to_targets("${CATCH_WARNING_TARGETS}")
+if(NIHIL_CONFIG)
+ add_subdirectory(nihil.config)
endif()
-# Only perform the installation steps when Catch is not being used as
-# a subproject via `add_subdirectory`, or the destinations will break,
-# see https://github.com/catchorg/Catch2/issues/1373
-if (NOT_SUBPROJECT)
- configure_package_config_file(
- ${CMAKE_CURRENT_LIST_DIR}/CMake/Catch2Config.cmake.in
- ${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake
- INSTALL_DESTINATION
- ${CATCH_CMAKE_CONFIG_DESTINATION}
- )
-
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake"
- COMPATIBILITY
- SameMajorVersion
- )
-
- install(
- FILES
- "${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake"
- "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake"
- DESTINATION
- ${CATCH_CMAKE_CONFIG_DESTINATION}
- )
-
- # Install documentation
- if(CATCH_INSTALL_DOCS)
- install(
- DIRECTORY
- docs/
- DESTINATION
- "${CMAKE_INSTALL_DOCDIR}"
- PATTERN "doxygen" EXCLUDE
- )
- endif()
-
- if(CATCH_INSTALL_EXTRAS)
- # Install CMake scripts
- install(
- FILES
- "extras/ParseAndAddCatchTests.cmake"
- "extras/Catch.cmake"
- "extras/CatchAddTests.cmake"
- "extras/CatchShardTests.cmake"
- "extras/CatchShardTestsImpl.cmake"
- DESTINATION
- ${CATCH_CMAKE_CONFIG_DESTINATION}
- )
-
- # Install debugger helpers
- install(
- FILES
- "extras/gdbinit"
- "extras/lldbinit"
- DESTINATION
- ${CMAKE_INSTALL_DATAROOTDIR}/Catch2
- )
- endif()
-
- ## Provide some pkg-config integration
- set(PKGCONFIG_INSTALL_DIR
- "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig"
- CACHE PATH "Path where catch2.pc is installed"
- )
- configure_file(
- ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2.pc.in
- ${CMAKE_CURRENT_BINARY_DIR}/catch2.pc
- @ONLY
- )
- configure_file(
- ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2-with-main.pc.in
- ${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc
- @ONLY
- )
- install(
- FILES
- "${CMAKE_CURRENT_BINARY_DIR}/catch2.pc"
- "${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc"
- DESTINATION
- ${PKGCONFIG_INSTALL_DIR}
- )
-
- set(CPACK_PACKAGE_CONTACT "https://github.com/catchorg/Catch2/")
-
-
- include( CPack )
-
+if(NIHIL_TESTS)
+ enable_testing()
endif()
diff --git a/README b/README
new file mode 100644
index 0000000..42471d0
--- /dev/null
+++ b/README
@@ -0,0 +1,28 @@
+nihil: C++ utility library
+==========================
+
+nihil is a C++ library which provides various utilities that might be useful
+in a C++ program. many of the utilities are specific to FreeBSD.
+
+i wrote this primarily for my own programs, but you're welcome to use it too.
+
+license
+-------
+
+all of nihil is in the public domain, with the exception of:
+
+- nihil/generator.ccm (BSL)
+- nihil/monad.ccm (MIT)
+- nihil/uuid.ccm (MIT)
+
+requirements
+------------
+
++ FreeBSD
++ a modern C++ compiler. nihil is tested using LLVM 19.x with -std=c++26.
+
+usage
+-----
+
+nihil is intended to be consumed as a CMake subdirectory. better install
+options might be added later.
diff --git a/clang-tidy.conf b/clang-tidy.conf
new file mode 100644
index 0000000..92a59bc
--- /dev/null
+++ b/clang-tidy.conf
@@ -0,0 +1,8 @@
+---
+WarningsAsErrors: '*'
+Checks: >
+ -*,
+ bugprone-*,
+ -bugprone-reserved-identifier,
+ -bugprone-easily-swappable-parameters,
+ modernize-*,
diff --git a/.bazelrc b/contrib/catch2/.bazelrc
index 6084470..6084470 100644
--- a/.bazelrc
+++ b/contrib/catch2/.bazelrc
diff --git a/contrib/catch2/.clang-format b/contrib/catch2/.clang-format
new file mode 100644
index 0000000..9efb854
--- /dev/null
+++ b/contrib/catch2/.clang-format
@@ -0,0 +1,45 @@
+---
+Language: Cpp
+Standard: c++14
+
+# Note that we cannot use IncludeIsMainRegex functionality, because it
+# does not support includes in angle brackets (<>)
+SortIncludes: true
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: <catch2/.*\.hpp>
+ Priority: 1
+ - Regex: <.*/.*\.hpp>
+ Priority: 2
+ - Regex: <.*>
+ Priority: 3
+
+AllowShortBlocksOnASingleLine: Always
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLambdasOnASingleLine: Inline
+
+AccessModifierOffset: "-4"
+AlignEscapedNewlines: Left
+AllowAllConstructorInitializersOnNextLine: "true"
+BinPackArguments: "false"
+BinPackParameters: "false"
+BreakConstructorInitializers: AfterColon
+ConstructorInitializerAllOnOneLineOrOnePerLine: "true"
+DerivePointerAlignment: "false"
+FixNamespaceComments: "true"
+IndentCaseLabels: "false"
+IndentPPDirectives: AfterHash
+IndentWidth: "4"
+NamespaceIndentation: All
+PointerAlignment: Left
+SpaceBeforeCtorInitializerColon: "false"
+SpaceInEmptyParentheses: "false"
+SpacesInParentheses: "true"
+TabWidth: "4"
+UseTab: Never
+AlwaysBreakTemplateDeclarations: Yes
+SpaceAfterTemplateKeyword: true
+SortUsingDeclarations: true
+ReflowComments: true
diff --git a/.clang-tidy b/contrib/catch2/.clang-tidy
index 3488457..3488457 100644
--- a/.clang-tidy
+++ b/contrib/catch2/.clang-tidy
diff --git a/.conan/build.py b/contrib/catch2/.conan/build.py
index e163d5f..e163d5f 100644
--- a/.conan/build.py
+++ b/contrib/catch2/.conan/build.py
diff --git a/.conan/test_package/CMakeLists.txt b/contrib/catch2/.conan/test_package/CMakeLists.txt
index f067457..f067457 100644
--- a/.conan/test_package/CMakeLists.txt
+++ b/contrib/catch2/.conan/test_package/CMakeLists.txt
diff --git a/.conan/test_package/conanfile.py b/contrib/catch2/.conan/test_package/conanfile.py
index dc03876..dc03876 100644
--- a/.conan/test_package/conanfile.py
+++ b/contrib/catch2/.conan/test_package/conanfile.py
diff --git a/.conan/test_package/test_package.cpp b/contrib/catch2/.conan/test_package/test_package.cpp
index 3c08090..3c08090 100644
--- a/.conan/test_package/test_package.cpp
+++ b/contrib/catch2/.conan/test_package/test_package.cpp
diff --git a/.gitattributes b/contrib/catch2/.gitattributes
index 23f98ff..23f98ff 100644
--- a/.gitattributes
+++ b/contrib/catch2/.gitattributes
diff --git a/.github/FUNDING.yml b/contrib/catch2/.github/FUNDING.yml
index 9122aa8..9122aa8 100644
--- a/.github/FUNDING.yml
+++ b/contrib/catch2/.github/FUNDING.yml
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/contrib/catch2/.github/ISSUE_TEMPLATE/bug_report.md
index dbeff11..dbeff11 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/contrib/catch2/.github/ISSUE_TEMPLATE/bug_report.md
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/contrib/catch2/.github/ISSUE_TEMPLATE/feature_request.md
index be9b9ee..be9b9ee 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/contrib/catch2/.github/ISSUE_TEMPLATE/feature_request.md
diff --git a/.github/pull_request_template.md b/contrib/catch2/.github/pull_request_template.md
index ea2b7bb..ea2b7bb 100644
--- a/.github/pull_request_template.md
+++ b/contrib/catch2/.github/pull_request_template.md
diff --git a/.github/workflows/linux-bazel-builds.yml b/contrib/catch2/.github/workflows/linux-bazel-builds.yml
index dc826ac..dc826ac 100644
--- a/.github/workflows/linux-bazel-builds.yml
+++ b/contrib/catch2/.github/workflows/linux-bazel-builds.yml
diff --git a/.github/workflows/linux-meson-builds.yml b/contrib/catch2/.github/workflows/linux-meson-builds.yml
index 4a6cfd5..4a6cfd5 100644
--- a/.github/workflows/linux-meson-builds.yml
+++ b/contrib/catch2/.github/workflows/linux-meson-builds.yml
diff --git a/.github/workflows/linux-other-builds.yml b/contrib/catch2/.github/workflows/linux-other-builds.yml
index 6993c81..6993c81 100644
--- a/.github/workflows/linux-other-builds.yml
+++ b/contrib/catch2/.github/workflows/linux-other-builds.yml
diff --git a/.github/workflows/linux-simple-builds.yml b/contrib/catch2/.github/workflows/linux-simple-builds.yml
index 4cca316..4cca316 100644
--- a/.github/workflows/linux-simple-builds.yml
+++ b/contrib/catch2/.github/workflows/linux-simple-builds.yml
diff --git a/.github/workflows/mac-builds-m1.yml b/contrib/catch2/.github/workflows/mac-builds-m1.yml
index 45629cd..45629cd 100644
--- a/.github/workflows/mac-builds-m1.yml
+++ b/contrib/catch2/.github/workflows/mac-builds-m1.yml
diff --git a/.github/workflows/mac-builds.yml b/contrib/catch2/.github/workflows/mac-builds.yml
index fb41f5d..fb41f5d 100644
--- a/.github/workflows/mac-builds.yml
+++ b/contrib/catch2/.github/workflows/mac-builds.yml
diff --git a/.github/workflows/package-manager-builds.yaml b/contrib/catch2/.github/workflows/package-manager-builds.yaml
index 6d90d14..6d90d14 100644
--- a/.github/workflows/package-manager-builds.yaml
+++ b/contrib/catch2/.github/workflows/package-manager-builds.yaml
diff --git a/.github/workflows/validate-header-guards.yml b/contrib/catch2/.github/workflows/validate-header-guards.yml
index fa9d157..fa9d157 100644
--- a/.github/workflows/validate-header-guards.yml
+++ b/contrib/catch2/.github/workflows/validate-header-guards.yml
diff --git a/.github/workflows/windows-simple-builds.yml b/contrib/catch2/.github/workflows/windows-simple-builds.yml
index 5fb7b8f..5fb7b8f 100644
--- a/.github/workflows/windows-simple-builds.yml
+++ b/contrib/catch2/.github/workflows/windows-simple-builds.yml
diff --git a/contrib/catch2/.gitignore b/contrib/catch2/.gitignore
new file mode 100644
index 0000000..dbf9f40
--- /dev/null
+++ b/contrib/catch2/.gitignore
@@ -0,0 +1,40 @@
+*.build
+!meson.build
+*.pbxuser
+*.mode1v3
+*.ncb
+*.suo
+Debug
+Release
+*.user
+*.xcuserstate
+.DS_Store
+xcuserdata
+CatchSelfTest.xcscheme
+Breakpoints.xcbkptlist
+UpgradeLog.XML
+Resources/DWARF
+projects/Generated
+*.pyc
+DerivedData
+*.xccheckout
+Build
+.idea
+.vs
+.vscode
+cmake-build-*
+benchmark-dir
+.conan/test_package/build
+**/CMakeUserPresets.json
+bazel-*
+MODULE.bazel.lock
+build-fuzzers
+debug-build
+.vscode
+msvc-sln*
+# Currently we use Doxygen for dep graphs and the full docs are only slowly
+# being filled in, so we definitely do not want git to deal with the docs.
+docs/doxygen
+*.cache
+compile_commands.json
+**/*.unapproved.txt
diff --git a/BUILD.bazel b/contrib/catch2/BUILD.bazel
index d96c7fa..d96c7fa 100644
--- a/BUILD.bazel
+++ b/contrib/catch2/BUILD.bazel
diff --git a/CMake/Catch2Config.cmake.in b/contrib/catch2/CMake/Catch2Config.cmake.in
index c485219..c485219 100644
--- a/CMake/Catch2Config.cmake.in
+++ b/contrib/catch2/CMake/Catch2Config.cmake.in
diff --git a/CMake/CatchConfigOptions.cmake b/contrib/catch2/CMake/CatchConfigOptions.cmake
index a2f2870..a2f2870 100644
--- a/CMake/CatchConfigOptions.cmake
+++ b/contrib/catch2/CMake/CatchConfigOptions.cmake
diff --git a/CMake/CatchMiscFunctions.cmake b/contrib/catch2/CMake/CatchMiscFunctions.cmake
index 05bc83c..05bc83c 100644
--- a/CMake/CatchMiscFunctions.cmake
+++ b/contrib/catch2/CMake/CatchMiscFunctions.cmake
diff --git a/CMake/FindGcov.cmake b/contrib/catch2/CMake/FindGcov.cmake
index 4141711..4141711 100644
--- a/CMake/FindGcov.cmake
+++ b/contrib/catch2/CMake/FindGcov.cmake
diff --git a/CMake/FindLcov.cmake b/contrib/catch2/CMake/FindLcov.cmake
index beb925a..beb925a 100644
--- a/CMake/FindLcov.cmake
+++ b/contrib/catch2/CMake/FindLcov.cmake
diff --git a/CMake/Findcodecov.cmake b/contrib/catch2/CMake/Findcodecov.cmake
index 2c0f2fe..2c0f2fe 100644
--- a/CMake/Findcodecov.cmake
+++ b/contrib/catch2/CMake/Findcodecov.cmake
diff --git a/CMake/catch2-with-main.pc.in b/contrib/catch2/CMake/catch2-with-main.pc.in
index 69a790b..69a790b 100644
--- a/CMake/catch2-with-main.pc.in
+++ b/contrib/catch2/CMake/catch2-with-main.pc.in
diff --git a/CMake/catch2.pc.in b/contrib/catch2/CMake/catch2.pc.in
index bd1c95a..bd1c95a 100644
--- a/CMake/catch2.pc.in
+++ b/contrib/catch2/CMake/catch2.pc.in
diff --git a/CMake/llvm-cov-wrapper b/contrib/catch2/CMake/llvm-cov-wrapper
index 2ac3310..2ac3310 100755
--- a/CMake/llvm-cov-wrapper
+++ b/contrib/catch2/CMake/llvm-cov-wrapper
diff --git a/contrib/catch2/CMakeLists.txt b/contrib/catch2/CMakeLists.txt
new file mode 100644
index 0000000..0d8226b
--- /dev/null
+++ b/contrib/catch2/CMakeLists.txt
@@ -0,0 +1,198 @@
+cmake_minimum_required(VERSION 3.16)
+
+# detect if Catch is being bundled,
+# disable testsuite in that case
+if(NOT DEFINED PROJECT_NAME)
+ set(NOT_SUBPROJECT ON)
+else()
+ set(NOT_SUBPROJECT OFF)
+endif()
+
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+option(CATCH_INSTALL_DOCS "Install documentation alongside library" ON)
+option(CATCH_INSTALL_EXTRAS "Install extras (CMake scripts, debugger helpers) alongside library" ON)
+option(CATCH_DEVELOPMENT_BUILD "Build tests, enable warnings, enable Werror, etc" OFF)
+option(CATCH_ENABLE_REPRODUCIBLE_BUILD "Add compiler flags for improving build reproducibility" ON)
+
+include(CMakeDependentOption)
+cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_ENABLE_CONFIGURE_TESTS "Enable CMake configuration tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+cmake_dependent_option(CATCH_ENABLE_CMAKE_HELPER_TESTS "Enable CMake helper tests. WARNING: VERY EXPENSIVE" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
+
+
+# Catch2's build breaks if done in-tree. You probably should not build
+# things in tree anyway, but we can allow projects that include Catch2
+# as a subproject to build in-tree as long as it is not in our tree.
+if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
+endif()
+
+project(Catch2
+ VERSION 3.8.1 # CML version placeholder, don't delete
+ LANGUAGES CXX
+ HOMEPAGE_URL "https://github.com/catchorg/Catch2"
+ DESCRIPTION "A modern, C++-native, unit test framework."
+)
+
+# Provide path for scripts. We first add path to the scripts we don't use,
+# but projects including us might, and set the path up to parent scope.
+# Then we also add path that we use to configure the project, but is of
+# no use to top level projects.
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/extras")
+if (NOT NOT_SUBPROJECT)
+ set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE)
+endif()
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
+
+include(GNUInstallDirs)
+include(CMakePackageConfigHelpers)
+include(CatchConfigOptions)
+if(CATCH_DEVELOPMENT_BUILD)
+ include(CTest)
+endif()
+
+# This variable is used in some subdirectories, so we need it here, rather
+# than later in the install block
+set(CATCH_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Catch2")
+
+# We have some Windows builds that test `wmain` entry point,
+# and we need this change to be present in all binaries that
+# are built during these tests, so this is required here, before
+# the subdirectories are added.
+if(CATCH_TEST_USE_WMAIN)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ENTRY:wmainCRTStartup")
+endif()
+
+
+# Basic paths
+set(CATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(SOURCES_DIR ${CATCH_DIR}/src/catch2)
+set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
+
+# We need to bring-in the variables defined there to this scope
+add_subdirectory(src)
+
+# Build tests only if requested
+if (BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
+ find_package(PythonInterp 3 REQUIRED)
+ if (NOT PYTHONINTERP_FOUND)
+ message(FATAL_ERROR "Python not found, but required for tests")
+ endif()
+ set(CMAKE_FOLDER "tests")
+ add_subdirectory(tests)
+endif()
+
+if(CATCH_BUILD_EXAMPLES)
+ set(CMAKE_FOLDER "Examples")
+ add_subdirectory(examples)
+endif()
+
+if(CATCH_BUILD_EXTRA_TESTS)
+ set(CMAKE_FOLDER "tests/ExtraTests")
+ add_subdirectory(tests/ExtraTests)
+endif()
+
+if(CATCH_BUILD_FUZZERS)
+ set(CMAKE_FOLDER "fuzzing")
+ add_subdirectory(fuzzing)
+endif()
+
+if (CATCH_DEVELOPMENT_BUILD)
+ add_warnings_to_targets("${CATCH_WARNING_TARGETS}")
+endif()
+
+# Only perform the installation steps when Catch is not being used as
+# a subproject via `add_subdirectory`, or the destinations will break,
+# see https://github.com/catchorg/Catch2/issues/1373
+if (NOT_SUBPROJECT)
+ configure_package_config_file(
+ ${CMAKE_CURRENT_LIST_DIR}/CMake/Catch2Config.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake
+ INSTALL_DESTINATION
+ ${CATCH_CMAKE_CONFIG_DESTINATION}
+ )
+
+ write_basic_package_version_file(
+ "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake"
+ COMPATIBILITY
+ SameMajorVersion
+ )
+
+ install(
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/Catch2Config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/Catch2ConfigVersion.cmake"
+ DESTINATION
+ ${CATCH_CMAKE_CONFIG_DESTINATION}
+ )
+
+ # Install documentation
+ if(CATCH_INSTALL_DOCS)
+ install(
+ DIRECTORY
+ docs/
+ DESTINATION
+ "${CMAKE_INSTALL_DOCDIR}"
+ PATTERN "doxygen" EXCLUDE
+ )
+ endif()
+
+ if(CATCH_INSTALL_EXTRAS)
+ # Install CMake scripts
+ install(
+ FILES
+ "extras/ParseAndAddCatchTests.cmake"
+ "extras/Catch.cmake"
+ "extras/CatchAddTests.cmake"
+ "extras/CatchShardTests.cmake"
+ "extras/CatchShardTestsImpl.cmake"
+ DESTINATION
+ ${CATCH_CMAKE_CONFIG_DESTINATION}
+ )
+
+ # Install debugger helpers
+ install(
+ FILES
+ "extras/gdbinit"
+ "extras/lldbinit"
+ DESTINATION
+ ${CMAKE_INSTALL_DATAROOTDIR}/Catch2
+ )
+ endif()
+
+ ## Provide some pkg-config integration
+ set(PKGCONFIG_INSTALL_DIR
+ "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig"
+ CACHE PATH "Path where catch2.pc is installed"
+ )
+ configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2.pc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/catch2.pc
+ @ONLY
+ )
+ configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/CMake/catch2-with-main.pc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc
+ @ONLY
+ )
+ install(
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/catch2.pc"
+ "${CMAKE_CURRENT_BINARY_DIR}/catch2-with-main.pc"
+ DESTINATION
+ ${PKGCONFIG_INSTALL_DIR}
+ )
+
+ set(CPACK_PACKAGE_CONTACT "https://github.com/catchorg/Catch2/")
+
+
+ include( CPack )
+
+endif()
diff --git a/CMakePresets.json b/contrib/catch2/CMakePresets.json
index 8854128..8854128 100644
--- a/CMakePresets.json
+++ b/contrib/catch2/CMakePresets.json
diff --git a/CODE_OF_CONDUCT.md b/contrib/catch2/CODE_OF_CONDUCT.md
index be1a688..be1a688 100644
--- a/CODE_OF_CONDUCT.md
+++ b/contrib/catch2/CODE_OF_CONDUCT.md
diff --git a/Doxyfile b/contrib/catch2/Doxyfile
index 914e598..914e598 100644
--- a/Doxyfile
+++ b/contrib/catch2/Doxyfile
diff --git a/LICENSE.txt b/contrib/catch2/LICENSE.txt
index 36b7cd9..36b7cd9 100644
--- a/LICENSE.txt
+++ b/contrib/catch2/LICENSE.txt
diff --git a/MODULE.bazel b/contrib/catch2/MODULE.bazel
index 628d77a..628d77a 100644
--- a/MODULE.bazel
+++ b/contrib/catch2/MODULE.bazel
diff --git a/README.md b/contrib/catch2/README.md
index 3ea54a5..3ea54a5 100644
--- a/README.md
+++ b/contrib/catch2/README.md
diff --git a/SECURITY.md b/contrib/catch2/SECURITY.md
index b66bf73..b66bf73 100644
--- a/SECURITY.md
+++ b/contrib/catch2/SECURITY.md
diff --git a/appveyor.yml b/contrib/catch2/appveyor.yml
index ba4556e..ba4556e 100644
--- a/appveyor.yml
+++ b/contrib/catch2/appveyor.yml
diff --git a/codecov.yml b/contrib/catch2/codecov.yml
index 357cb5e..357cb5e 100644
--- a/codecov.yml
+++ b/contrib/catch2/codecov.yml
diff --git a/conanfile.py b/contrib/catch2/conanfile.py
index 7a3ac7c..7a3ac7c 100755
--- a/conanfile.py
+++ b/contrib/catch2/conanfile.py
diff --git a/data/artwork/catch2-c-logo.png b/contrib/catch2/data/artwork/catch2-c-logo.png
index b1066b8..b1066b8 100644
--- a/data/artwork/catch2-c-logo.png
+++ b/contrib/catch2/data/artwork/catch2-c-logo.png
Binary files differ
diff --git a/data/artwork/catch2-hand-logo.png b/contrib/catch2/data/artwork/catch2-hand-logo.png
index ab857ea..ab857ea 100644
--- a/data/artwork/catch2-hand-logo.png
+++ b/contrib/catch2/data/artwork/catch2-hand-logo.png
Binary files differ
diff --git a/data/artwork/catch2-logo-small-with-background.png b/contrib/catch2/data/artwork/catch2-logo-small-with-background.png
index 963eaf4..963eaf4 100644
--- a/data/artwork/catch2-logo-small-with-background.png
+++ b/contrib/catch2/data/artwork/catch2-logo-small-with-background.png
Binary files differ
diff --git a/data/artwork/catch2-logo-small.png b/contrib/catch2/data/artwork/catch2-logo-small.png
index 742e81e..742e81e 100644
--- a/data/artwork/catch2-logo-small.png
+++ b/contrib/catch2/data/artwork/catch2-logo-small.png
Binary files differ
diff --git a/docs/Readme.md b/contrib/catch2/docs/Readme.md
index d84b4bf..d84b4bf 100644
--- a/docs/Readme.md
+++ b/contrib/catch2/docs/Readme.md
diff --git a/docs/assertions.md b/contrib/catch2/docs/assertions.md
index f3dcdd4..f3dcdd4 100644
--- a/docs/assertions.md
+++ b/contrib/catch2/docs/assertions.md
diff --git a/docs/benchmarks.md b/contrib/catch2/docs/benchmarks.md
index 9edbb93..9edbb93 100644
--- a/docs/benchmarks.md
+++ b/contrib/catch2/docs/benchmarks.md
diff --git a/docs/ci-and-misc.md b/contrib/catch2/docs/ci-and-misc.md
index 49bbd98..49bbd98 100644
--- a/docs/ci-and-misc.md
+++ b/contrib/catch2/docs/ci-and-misc.md
diff --git a/docs/cmake-integration.md b/contrib/catch2/docs/cmake-integration.md
index cfb9853..cfb9853 100644
--- a/docs/cmake-integration.md
+++ b/contrib/catch2/docs/cmake-integration.md
diff --git a/docs/command-line.md b/contrib/catch2/docs/command-line.md
index 7e69bf1..7e69bf1 100644
--- a/docs/command-line.md
+++ b/contrib/catch2/docs/command-line.md
diff --git a/docs/commercial-users.md b/contrib/catch2/docs/commercial-users.md
index 020eaef..020eaef 100644
--- a/docs/commercial-users.md
+++ b/contrib/catch2/docs/commercial-users.md
diff --git a/docs/comparing-floating-point-numbers.md b/contrib/catch2/docs/comparing-floating-point-numbers.md
index ab5ba6d..ab5ba6d 100644
--- a/docs/comparing-floating-point-numbers.md
+++ b/contrib/catch2/docs/comparing-floating-point-numbers.md
diff --git a/docs/configuration.md b/contrib/catch2/docs/configuration.md
index f268ba5..f268ba5 100644
--- a/docs/configuration.md
+++ b/contrib/catch2/docs/configuration.md
diff --git a/docs/contributing.md b/contrib/catch2/docs/contributing.md
index d21323d..d21323d 100644
--- a/docs/contributing.md
+++ b/contrib/catch2/docs/contributing.md
diff --git a/docs/deprecations.md b/contrib/catch2/docs/deprecations.md
index 0b5bee1..0b5bee1 100644
--- a/docs/deprecations.md
+++ b/contrib/catch2/docs/deprecations.md
diff --git a/docs/event-listeners.md b/contrib/catch2/docs/event-listeners.md
index 71db3e1..71db3e1 100644
--- a/docs/event-listeners.md
+++ b/contrib/catch2/docs/event-listeners.md
diff --git a/docs/faq.md b/contrib/catch2/docs/faq.md
index 80923d2..80923d2 100644
--- a/docs/faq.md
+++ b/contrib/catch2/docs/faq.md
diff --git a/docs/generators.md b/contrib/catch2/docs/generators.md
index eb1a255..eb1a255 100644
--- a/docs/generators.md
+++ b/contrib/catch2/docs/generators.md
diff --git a/docs/limitations.md b/contrib/catch2/docs/limitations.md
index f5f60ba..f5f60ba 100644
--- a/docs/limitations.md
+++ b/contrib/catch2/docs/limitations.md
diff --git a/docs/list-of-examples.md b/contrib/catch2/docs/list-of-examples.md
index 40d3f71..40d3f71 100644
--- a/docs/list-of-examples.md
+++ b/contrib/catch2/docs/list-of-examples.md
diff --git a/docs/logging.md b/contrib/catch2/docs/logging.md
index 7970938..7970938 100644
--- a/docs/logging.md
+++ b/contrib/catch2/docs/logging.md
diff --git a/docs/matchers.md b/contrib/catch2/docs/matchers.md
index 4b9445a..4b9445a 100644
--- a/docs/matchers.md
+++ b/contrib/catch2/docs/matchers.md
diff --git a/docs/migrate-v2-to-v3.md b/contrib/catch2/docs/migrate-v2-to-v3.md
index 84ed769..84ed769 100644
--- a/docs/migrate-v2-to-v3.md
+++ b/contrib/catch2/docs/migrate-v2-to-v3.md
diff --git a/docs/opensource-users.md b/contrib/catch2/docs/opensource-users.md
index a02d0b9..a02d0b9 100644
--- a/docs/opensource-users.md
+++ b/contrib/catch2/docs/opensource-users.md
diff --git a/docs/other-macros.md b/contrib/catch2/docs/other-macros.md
index 79990a6..79990a6 100644
--- a/docs/other-macros.md
+++ b/contrib/catch2/docs/other-macros.md
diff --git a/docs/own-main.md b/contrib/catch2/docs/own-main.md
index 26dfa86..26dfa86 100644
--- a/docs/own-main.md
+++ b/contrib/catch2/docs/own-main.md
diff --git a/docs/release-notes.md b/contrib/catch2/docs/release-notes.md
index a393a52..a393a52 100644
--- a/docs/release-notes.md
+++ b/contrib/catch2/docs/release-notes.md
diff --git a/docs/release-process.md b/contrib/catch2/docs/release-process.md
index 12d23b7..12d23b7 100644
--- a/docs/release-process.md
+++ b/contrib/catch2/docs/release-process.md
diff --git a/docs/reporter-events.md b/contrib/catch2/docs/reporter-events.md
index 015f67b..015f67b 100644
--- a/docs/reporter-events.md
+++ b/contrib/catch2/docs/reporter-events.md
diff --git a/docs/reporters.md b/contrib/catch2/docs/reporters.md
index 20ef5e5..20ef5e5 100644
--- a/docs/reporters.md
+++ b/contrib/catch2/docs/reporters.md
diff --git a/docs/skipping-passing-failing.md b/contrib/catch2/docs/skipping-passing-failing.md
index 52bb18f..52bb18f 100644
--- a/docs/skipping-passing-failing.md
+++ b/contrib/catch2/docs/skipping-passing-failing.md
diff --git a/docs/test-cases-and-sections.md b/contrib/catch2/docs/test-cases-and-sections.md
index 14db55a..14db55a 100644
--- a/docs/test-cases-and-sections.md
+++ b/contrib/catch2/docs/test-cases-and-sections.md
diff --git a/docs/test-fixtures.md b/contrib/catch2/docs/test-fixtures.md
index 653b50e..653b50e 100644
--- a/docs/test-fixtures.md
+++ b/contrib/catch2/docs/test-fixtures.md
diff --git a/docs/tostring.md b/contrib/catch2/docs/tostring.md
index 513c1b4..513c1b4 100644
--- a/docs/tostring.md
+++ b/contrib/catch2/docs/tostring.md
diff --git a/docs/tutorial.md b/contrib/catch2/docs/tutorial.md
index fb5a5b3..fb5a5b3 100644
--- a/docs/tutorial.md
+++ b/contrib/catch2/docs/tutorial.md
diff --git a/docs/usage-tips.md b/contrib/catch2/docs/usage-tips.md
index 6be01ee..6be01ee 100644
--- a/docs/usage-tips.md
+++ b/contrib/catch2/docs/usage-tips.md
diff --git a/docs/why-catch.md b/contrib/catch2/docs/why-catch.md
index b736749..b736749 100644
--- a/docs/why-catch.md
+++ b/contrib/catch2/docs/why-catch.md
diff --git a/examples/010-TestCase.cpp b/contrib/catch2/examples/010-TestCase.cpp
index 9e5cd8c..9e5cd8c 100644
--- a/examples/010-TestCase.cpp
+++ b/contrib/catch2/examples/010-TestCase.cpp
diff --git a/examples/020-TestCase-1.cpp b/contrib/catch2/examples/020-TestCase-1.cpp
index a9d87db..a9d87db 100644
--- a/examples/020-TestCase-1.cpp
+++ b/contrib/catch2/examples/020-TestCase-1.cpp
diff --git a/examples/020-TestCase-2.cpp b/contrib/catch2/examples/020-TestCase-2.cpp
index 72dd0ff..72dd0ff 100644
--- a/examples/020-TestCase-2.cpp
+++ b/contrib/catch2/examples/020-TestCase-2.cpp
diff --git a/examples/030-Asn-Require-Check.cpp b/contrib/catch2/examples/030-Asn-Require-Check.cpp
index 62cd3cf..62cd3cf 100644
--- a/examples/030-Asn-Require-Check.cpp
+++ b/contrib/catch2/examples/030-Asn-Require-Check.cpp
diff --git a/examples/100-Fix-Section.cpp b/contrib/catch2/examples/100-Fix-Section.cpp
index 7c8d8aa..7c8d8aa 100644
--- a/examples/100-Fix-Section.cpp
+++ b/contrib/catch2/examples/100-Fix-Section.cpp
diff --git a/examples/110-Fix-ClassFixture.cpp b/contrib/catch2/examples/110-Fix-ClassFixture.cpp
index 614c379..614c379 100644
--- a/examples/110-Fix-ClassFixture.cpp
+++ b/contrib/catch2/examples/110-Fix-ClassFixture.cpp
diff --git a/examples/111-Fix-PersistentFixture.cpp b/contrib/catch2/examples/111-Fix-PersistentFixture.cpp
index 2bef90f..2bef90f 100644
--- a/examples/111-Fix-PersistentFixture.cpp
+++ b/contrib/catch2/examples/111-Fix-PersistentFixture.cpp
diff --git a/examples/120-Bdd-ScenarioGivenWhenThen.cpp b/contrib/catch2/examples/120-Bdd-ScenarioGivenWhenThen.cpp
index 345d53c..345d53c 100644
--- a/examples/120-Bdd-ScenarioGivenWhenThen.cpp
+++ b/contrib/catch2/examples/120-Bdd-ScenarioGivenWhenThen.cpp
diff --git a/examples/210-Evt-EventListeners.cpp b/contrib/catch2/examples/210-Evt-EventListeners.cpp
index d05dfaa..d05dfaa 100644
--- a/examples/210-Evt-EventListeners.cpp
+++ b/contrib/catch2/examples/210-Evt-EventListeners.cpp
diff --git a/examples/231-Cfg-OutputStreams.cpp b/contrib/catch2/examples/231-Cfg-OutputStreams.cpp
index 5aee38b..5aee38b 100644
--- a/examples/231-Cfg-OutputStreams.cpp
+++ b/contrib/catch2/examples/231-Cfg-OutputStreams.cpp
diff --git a/examples/232-Cfg-CustomMain.cpp b/contrib/catch2/examples/232-Cfg-CustomMain.cpp
index 2704099..2704099 100644
--- a/examples/232-Cfg-CustomMain.cpp
+++ b/contrib/catch2/examples/232-Cfg-CustomMain.cpp
diff --git a/examples/300-Gen-OwnGenerator.cpp b/contrib/catch2/examples/300-Gen-OwnGenerator.cpp
index 9cb02e3..9cb02e3 100644
--- a/examples/300-Gen-OwnGenerator.cpp
+++ b/contrib/catch2/examples/300-Gen-OwnGenerator.cpp
diff --git a/examples/301-Gen-MapTypeConversion.cpp b/contrib/catch2/examples/301-Gen-MapTypeConversion.cpp
index 0a28448..0a28448 100644
--- a/examples/301-Gen-MapTypeConversion.cpp
+++ b/contrib/catch2/examples/301-Gen-MapTypeConversion.cpp
diff --git a/examples/302-Gen-Table.cpp b/contrib/catch2/examples/302-Gen-Table.cpp
index 3cdb143..3cdb143 100644
--- a/examples/302-Gen-Table.cpp
+++ b/contrib/catch2/examples/302-Gen-Table.cpp
diff --git a/examples/310-Gen-VariablesInGenerators.cpp b/contrib/catch2/examples/310-Gen-VariablesInGenerators.cpp
index 5d24d45..5d24d45 100644
--- a/examples/310-Gen-VariablesInGenerators.cpp
+++ b/contrib/catch2/examples/310-Gen-VariablesInGenerators.cpp
diff --git a/examples/311-Gen-CustomCapture.cpp b/contrib/catch2/examples/311-Gen-CustomCapture.cpp
index ee31038..ee31038 100644
--- a/examples/311-Gen-CustomCapture.cpp
+++ b/contrib/catch2/examples/311-Gen-CustomCapture.cpp
diff --git a/examples/CMakeLists.txt b/contrib/catch2/examples/CMakeLists.txt
index d45ddfc..d45ddfc 100644
--- a/examples/CMakeLists.txt
+++ b/contrib/catch2/examples/CMakeLists.txt
diff --git a/extras/Catch.cmake b/contrib/catch2/extras/Catch.cmake
index 3d93fe2..3d93fe2 100644
--- a/extras/Catch.cmake
+++ b/contrib/catch2/extras/Catch.cmake
diff --git a/extras/CatchAddTests.cmake b/contrib/catch2/extras/CatchAddTests.cmake
index 2534213..2534213 100644
--- a/extras/CatchAddTests.cmake
+++ b/contrib/catch2/extras/CatchAddTests.cmake
diff --git a/extras/CatchShardTests.cmake b/contrib/catch2/extras/CatchShardTests.cmake
index 68228f5..68228f5 100644
--- a/extras/CatchShardTests.cmake
+++ b/contrib/catch2/extras/CatchShardTests.cmake
diff --git a/extras/CatchShardTestsImpl.cmake b/contrib/catch2/extras/CatchShardTestsImpl.cmake
index bb2fc3e..bb2fc3e 100644
--- a/extras/CatchShardTestsImpl.cmake
+++ b/contrib/catch2/extras/CatchShardTestsImpl.cmake
diff --git a/extras/ParseAndAddCatchTests.cmake b/contrib/catch2/extras/ParseAndAddCatchTests.cmake
index 31fc193..31fc193 100644
--- a/extras/ParseAndAddCatchTests.cmake
+++ b/contrib/catch2/extras/ParseAndAddCatchTests.cmake
diff --git a/extras/catch_amalgamated.cpp b/contrib/catch2/extras/catch_amalgamated.cpp
index b979eb2..b979eb2 100644
--- a/extras/catch_amalgamated.cpp
+++ b/contrib/catch2/extras/catch_amalgamated.cpp
diff --git a/extras/catch_amalgamated.hpp b/contrib/catch2/extras/catch_amalgamated.hpp
index 7703e95..7703e95 100644
--- a/extras/catch_amalgamated.hpp
+++ b/contrib/catch2/extras/catch_amalgamated.hpp
diff --git a/extras/gdbinit b/contrib/catch2/extras/gdbinit
index fb3608a..fb3608a 100644
--- a/extras/gdbinit
+++ b/contrib/catch2/extras/gdbinit
diff --git a/extras/lldbinit b/contrib/catch2/extras/lldbinit
index 4f13634..4f13634 100644
--- a/extras/lldbinit
+++ b/contrib/catch2/extras/lldbinit
diff --git a/fuzzing/CMakeLists.txt b/contrib/catch2/fuzzing/CMakeLists.txt
index daba61d..daba61d 100644
--- a/fuzzing/CMakeLists.txt
+++ b/contrib/catch2/fuzzing/CMakeLists.txt
diff --git a/fuzzing/NullOStream.cpp b/contrib/catch2/fuzzing/NullOStream.cpp
index e3a181e..e3a181e 100644
--- a/fuzzing/NullOStream.cpp
+++ b/contrib/catch2/fuzzing/NullOStream.cpp
diff --git a/fuzzing/NullOStream.h b/contrib/catch2/fuzzing/NullOStream.h
index abbec09..abbec09 100644
--- a/fuzzing/NullOStream.h
+++ b/contrib/catch2/fuzzing/NullOStream.h
diff --git a/fuzzing/build_fuzzers.sh b/contrib/catch2/fuzzing/build_fuzzers.sh
index 9788c68..9788c68 100755
--- a/fuzzing/build_fuzzers.sh
+++ b/contrib/catch2/fuzzing/build_fuzzers.sh
diff --git a/fuzzing/fuzz_TestSpecParser.cpp b/contrib/catch2/fuzzing/fuzz_TestSpecParser.cpp
index 3aba8c8..3aba8c8 100644
--- a/fuzzing/fuzz_TestSpecParser.cpp
+++ b/contrib/catch2/fuzzing/fuzz_TestSpecParser.cpp
diff --git a/fuzzing/fuzz_XmlWriter.cpp b/contrib/catch2/fuzzing/fuzz_XmlWriter.cpp
index 70c4ed8..70c4ed8 100644
--- a/fuzzing/fuzz_XmlWriter.cpp
+++ b/contrib/catch2/fuzzing/fuzz_XmlWriter.cpp
diff --git a/fuzzing/fuzz_textflow.cpp b/contrib/catch2/fuzzing/fuzz_textflow.cpp
index 7000f42..7000f42 100644
--- a/fuzzing/fuzz_textflow.cpp
+++ b/contrib/catch2/fuzzing/fuzz_textflow.cpp
diff --git a/mdsnippets.json b/contrib/catch2/mdsnippets.json
index 5a60dad..5a60dad 100644
--- a/mdsnippets.json
+++ b/contrib/catch2/mdsnippets.json
diff --git a/meson.build b/contrib/catch2/meson.build
index 6346d44..6346d44 100644
--- a/meson.build
+++ b/contrib/catch2/meson.build
diff --git a/meson_options.txt b/contrib/catch2/meson_options.txt
index b460f2d..b460f2d 100644
--- a/meson_options.txt
+++ b/contrib/catch2/meson_options.txt
diff --git a/src/CMakeLists.txt b/contrib/catch2/src/CMakeLists.txt
index c40de04..c40de04 100644
--- a/src/CMakeLists.txt
+++ b/contrib/catch2/src/CMakeLists.txt
diff --git a/src/catch2/benchmark/catch_benchmark.hpp b/contrib/catch2/src/catch2/benchmark/catch_benchmark.hpp
index d0f88cf..d0f88cf 100644
--- a/src/catch2/benchmark/catch_benchmark.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_benchmark.hpp
diff --git a/src/catch2/benchmark/catch_benchmark_all.hpp b/contrib/catch2/src/catch2/benchmark/catch_benchmark_all.hpp
index 56fc7c7..56fc7c7 100644
--- a/src/catch2/benchmark/catch_benchmark_all.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_benchmark_all.hpp
diff --git a/src/catch2/benchmark/catch_chronometer.cpp b/contrib/catch2/src/catch2/benchmark/catch_chronometer.cpp
index 92f03c9..92f03c9 100644
--- a/src/catch2/benchmark/catch_chronometer.cpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_chronometer.cpp
diff --git a/src/catch2/benchmark/catch_chronometer.hpp b/contrib/catch2/src/catch2/benchmark/catch_chronometer.hpp
index 95498e6..95498e6 100644
--- a/src/catch2/benchmark/catch_chronometer.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_chronometer.hpp
diff --git a/src/catch2/benchmark/catch_clock.hpp b/contrib/catch2/src/catch2/benchmark/catch_clock.hpp
index 4068c4d..4068c4d 100644
--- a/src/catch2/benchmark/catch_clock.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_clock.hpp
diff --git a/src/catch2/benchmark/catch_constructor.hpp b/contrib/catch2/src/catch2/benchmark/catch_constructor.hpp
index 853bd6c..853bd6c 100644
--- a/src/catch2/benchmark/catch_constructor.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_constructor.hpp
diff --git a/src/catch2/benchmark/catch_environment.hpp b/contrib/catch2/src/catch2/benchmark/catch_environment.hpp
index da3f2fa..da3f2fa 100644
--- a/src/catch2/benchmark/catch_environment.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_environment.hpp
diff --git a/src/catch2/benchmark/catch_estimate.hpp b/contrib/catch2/src/catch2/benchmark/catch_estimate.hpp
index 64383a2..64383a2 100644
--- a/src/catch2/benchmark/catch_estimate.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_estimate.hpp
diff --git a/src/catch2/benchmark/catch_execution_plan.hpp b/contrib/catch2/src/catch2/benchmark/catch_execution_plan.hpp
index 17ca589..17ca589 100644
--- a/src/catch2/benchmark/catch_execution_plan.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_execution_plan.hpp
diff --git a/src/catch2/benchmark/catch_optimizer.hpp b/contrib/catch2/src/catch2/benchmark/catch_optimizer.hpp
index 61e6571..61e6571 100644
--- a/src/catch2/benchmark/catch_optimizer.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_optimizer.hpp
diff --git a/src/catch2/benchmark/catch_outlier_classification.hpp b/contrib/catch2/src/catch2/benchmark/catch_outlier_classification.hpp
index b9a1178..b9a1178 100644
--- a/src/catch2/benchmark/catch_outlier_classification.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_outlier_classification.hpp
diff --git a/src/catch2/benchmark/catch_sample_analysis.hpp b/contrib/catch2/src/catch2/benchmark/catch_sample_analysis.hpp
index aeb87d0..aeb87d0 100644
--- a/src/catch2/benchmark/catch_sample_analysis.hpp
+++ b/contrib/catch2/src/catch2/benchmark/catch_sample_analysis.hpp
diff --git a/src/catch2/benchmark/detail/catch_analyse.cpp b/contrib/catch2/src/catch2/benchmark/detail/catch_analyse.cpp
index 14d7f45..14d7f45 100644
--- a/src/catch2/benchmark/detail/catch_analyse.cpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_analyse.cpp
diff --git a/src/catch2/benchmark/detail/catch_analyse.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_analyse.hpp
index 5e3f7b0..5e3f7b0 100644
--- a/src/catch2/benchmark/detail/catch_analyse.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_analyse.hpp
diff --git a/src/catch2/benchmark/detail/catch_benchmark_function.cpp b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.cpp
index 66d4e61..66d4e61 100644
--- a/src/catch2/benchmark/detail/catch_benchmark_function.cpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.cpp
diff --git a/src/catch2/benchmark/detail/catch_benchmark_function.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.hpp
index a03cb11..a03cb11 100644
--- a/src/catch2/benchmark/detail/catch_benchmark_function.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_function.hpp
diff --git a/src/catch2/benchmark/detail/catch_benchmark_stats.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats.hpp
index 3633bc9..3633bc9 100644
--- a/src/catch2/benchmark/detail/catch_benchmark_stats.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats.hpp
diff --git a/src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp
index 2ccc25d..2ccc25d 100644
--- a/src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp
diff --git a/src/catch2/benchmark/detail/catch_complete_invoke.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_complete_invoke.hpp
index 4dff4b7..4dff4b7 100644
--- a/src/catch2/benchmark/detail/catch_complete_invoke.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_complete_invoke.hpp
diff --git a/src/catch2/benchmark/detail/catch_estimate_clock.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_estimate_clock.hpp
index 6da24ce..6da24ce 100644
--- a/src/catch2/benchmark/detail/catch_estimate_clock.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_estimate_clock.hpp
diff --git a/src/catch2/benchmark/detail/catch_measure.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_measure.hpp
index a804907..a804907 100644
--- a/src/catch2/benchmark/detail/catch_measure.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_measure.hpp
diff --git a/src/catch2/benchmark/detail/catch_repeat.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_repeat.hpp
index 08c0337..08c0337 100644
--- a/src/catch2/benchmark/detail/catch_repeat.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_repeat.hpp
diff --git a/src/catch2/benchmark/detail/catch_run_for_at_least.cpp b/contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.cpp
index 3ebdcc0..3ebdcc0 100644
--- a/src/catch2/benchmark/detail/catch_run_for_at_least.cpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.cpp
diff --git a/src/catch2/benchmark/detail/catch_run_for_at_least.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.hpp
index 4dfa8bb..4dfa8bb 100644
--- a/src/catch2/benchmark/detail/catch_run_for_at_least.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_run_for_at_least.hpp
diff --git a/src/catch2/benchmark/detail/catch_stats.cpp b/contrib/catch2/src/catch2/benchmark/detail/catch_stats.cpp
index 2a5a2e0..2a5a2e0 100644
--- a/src/catch2/benchmark/detail/catch_stats.cpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_stats.cpp
diff --git a/src/catch2/benchmark/detail/catch_stats.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_stats.hpp
index 3bea612..3bea612 100644
--- a/src/catch2/benchmark/detail/catch_stats.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_stats.hpp
diff --git a/src/catch2/benchmark/detail/catch_timing.hpp b/contrib/catch2/src/catch2/benchmark/detail/catch_timing.hpp
index da56719..da56719 100644
--- a/src/catch2/benchmark/detail/catch_timing.hpp
+++ b/contrib/catch2/src/catch2/benchmark/detail/catch_timing.hpp
diff --git a/src/catch2/catch_all.hpp b/contrib/catch2/src/catch2/catch_all.hpp
index 5715995..5715995 100644
--- a/src/catch2/catch_all.hpp
+++ b/contrib/catch2/src/catch2/catch_all.hpp
diff --git a/src/catch2/catch_approx.cpp b/contrib/catch2/src/catch2/catch_approx.cpp
index 08c6799..08c6799 100644
--- a/src/catch2/catch_approx.cpp
+++ b/contrib/catch2/src/catch2/catch_approx.cpp
diff --git a/src/catch2/catch_approx.hpp b/contrib/catch2/src/catch2/catch_approx.hpp
index de4d2ab..de4d2ab 100644
--- a/src/catch2/catch_approx.hpp
+++ b/contrib/catch2/src/catch2/catch_approx.hpp
diff --git a/src/catch2/catch_assertion_info.hpp b/contrib/catch2/src/catch2/catch_assertion_info.hpp
index 9d2f91f..9d2f91f 100644
--- a/src/catch2/catch_assertion_info.hpp
+++ b/contrib/catch2/src/catch2/catch_assertion_info.hpp
diff --git a/src/catch2/catch_assertion_result.cpp b/contrib/catch2/src/catch2/catch_assertion_result.cpp
index dba8622..dba8622 100644
--- a/src/catch2/catch_assertion_result.cpp
+++ b/contrib/catch2/src/catch2/catch_assertion_result.cpp
diff --git a/src/catch2/catch_assertion_result.hpp b/contrib/catch2/src/catch2/catch_assertion_result.hpp
index 4888287..4888287 100644
--- a/src/catch2/catch_assertion_result.hpp
+++ b/contrib/catch2/src/catch2/catch_assertion_result.hpp
diff --git a/src/catch2/catch_case_sensitive.hpp b/contrib/catch2/src/catch2/catch_case_sensitive.hpp
index cc89110..cc89110 100644
--- a/src/catch2/catch_case_sensitive.hpp
+++ b/contrib/catch2/src/catch2/catch_case_sensitive.hpp
diff --git a/src/catch2/catch_config.cpp b/contrib/catch2/src/catch2/catch_config.cpp
index 352c1f4..352c1f4 100644
--- a/src/catch2/catch_config.cpp
+++ b/contrib/catch2/src/catch2/catch_config.cpp
diff --git a/src/catch2/catch_config.hpp b/contrib/catch2/src/catch2/catch_config.hpp
index 17e983e..17e983e 100644
--- a/src/catch2/catch_config.hpp
+++ b/contrib/catch2/src/catch2/catch_config.hpp
diff --git a/src/catch2/catch_get_random_seed.cpp b/contrib/catch2/src/catch2/catch_get_random_seed.cpp
index b122516..b122516 100644
--- a/src/catch2/catch_get_random_seed.cpp
+++ b/contrib/catch2/src/catch2/catch_get_random_seed.cpp
diff --git a/src/catch2/catch_get_random_seed.hpp b/contrib/catch2/src/catch2/catch_get_random_seed.hpp
index 3ab2a4a..3ab2a4a 100644
--- a/src/catch2/catch_get_random_seed.hpp
+++ b/contrib/catch2/src/catch2/catch_get_random_seed.hpp
diff --git a/src/catch2/catch_message.cpp b/contrib/catch2/src/catch2/catch_message.cpp
index 7b09ab8..7b09ab8 100644
--- a/src/catch2/catch_message.cpp
+++ b/contrib/catch2/src/catch2/catch_message.cpp
diff --git a/src/catch2/catch_message.hpp b/contrib/catch2/src/catch2/catch_message.hpp
index 0a73834..0a73834 100644
--- a/src/catch2/catch_message.hpp
+++ b/contrib/catch2/src/catch2/catch_message.hpp
diff --git a/src/catch2/catch_registry_hub.cpp b/contrib/catch2/src/catch2/catch_registry_hub.cpp
index 3a59467..3a59467 100644
--- a/src/catch2/catch_registry_hub.cpp
+++ b/contrib/catch2/src/catch2/catch_registry_hub.cpp
diff --git a/src/catch2/catch_section_info.hpp b/contrib/catch2/src/catch2/catch_section_info.hpp
index 7de8441..7de8441 100644
--- a/src/catch2/catch_section_info.hpp
+++ b/contrib/catch2/src/catch2/catch_section_info.hpp
diff --git a/src/catch2/catch_session.cpp b/contrib/catch2/src/catch2/catch_session.cpp
index a4a410c..a4a410c 100644
--- a/src/catch2/catch_session.cpp
+++ b/contrib/catch2/src/catch2/catch_session.cpp
diff --git a/src/catch2/catch_session.hpp b/contrib/catch2/src/catch2/catch_session.hpp
index c1de6d5..c1de6d5 100644
--- a/src/catch2/catch_session.hpp
+++ b/contrib/catch2/src/catch2/catch_session.hpp
diff --git a/src/catch2/catch_tag_alias.hpp b/contrib/catch2/src/catch2/catch_tag_alias.hpp
index dc91f21..dc91f21 100644
--- a/src/catch2/catch_tag_alias.hpp
+++ b/contrib/catch2/src/catch2/catch_tag_alias.hpp
diff --git a/src/catch2/catch_tag_alias_autoregistrar.cpp b/contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.cpp
index 9b6633a..9b6633a 100644
--- a/src/catch2/catch_tag_alias_autoregistrar.cpp
+++ b/contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.cpp
diff --git a/src/catch2/catch_tag_alias_autoregistrar.hpp b/contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.hpp
index 9f80f72..9f80f72 100644
--- a/src/catch2/catch_tag_alias_autoregistrar.hpp
+++ b/contrib/catch2/src/catch2/catch_tag_alias_autoregistrar.hpp
diff --git a/src/catch2/catch_template_test_macros.hpp b/contrib/catch2/src/catch2/catch_template_test_macros.hpp
index 3baee51..3baee51 100644
--- a/src/catch2/catch_template_test_macros.hpp
+++ b/contrib/catch2/src/catch2/catch_template_test_macros.hpp
diff --git a/src/catch2/catch_test_case_info.cpp b/contrib/catch2/src/catch2/catch_test_case_info.cpp
index 9d64e53..9d64e53 100644
--- a/src/catch2/catch_test_case_info.cpp
+++ b/contrib/catch2/src/catch2/catch_test_case_info.cpp
diff --git a/src/catch2/catch_test_case_info.hpp b/contrib/catch2/src/catch2/catch_test_case_info.hpp
index 3466660..3466660 100644
--- a/src/catch2/catch_test_case_info.hpp
+++ b/contrib/catch2/src/catch2/catch_test_case_info.hpp
diff --git a/src/catch2/catch_test_macros.hpp b/contrib/catch2/src/catch2/catch_test_macros.hpp
index 6ee2129..6ee2129 100644
--- a/src/catch2/catch_test_macros.hpp
+++ b/contrib/catch2/src/catch2/catch_test_macros.hpp
diff --git a/src/catch2/catch_test_spec.cpp b/contrib/catch2/src/catch2/catch_test_spec.cpp
index f32f986..f32f986 100644
--- a/src/catch2/catch_test_spec.cpp
+++ b/contrib/catch2/src/catch2/catch_test_spec.cpp
diff --git a/src/catch2/catch_test_spec.hpp b/contrib/catch2/src/catch2/catch_test_spec.hpp
index f12baa6..f12baa6 100644
--- a/src/catch2/catch_test_spec.hpp
+++ b/contrib/catch2/src/catch2/catch_test_spec.hpp
diff --git a/src/catch2/catch_timer.cpp b/contrib/catch2/src/catch2/catch_timer.cpp
index efdd8b7..efdd8b7 100644
--- a/src/catch2/catch_timer.cpp
+++ b/contrib/catch2/src/catch2/catch_timer.cpp
diff --git a/src/catch2/catch_timer.hpp b/contrib/catch2/src/catch2/catch_timer.hpp
index f42589f..f42589f 100644
--- a/src/catch2/catch_timer.hpp
+++ b/contrib/catch2/src/catch2/catch_timer.hpp
diff --git a/src/catch2/catch_tostring.cpp b/contrib/catch2/src/catch2/catch_tostring.cpp
index 83327cf..83327cf 100644
--- a/src/catch2/catch_tostring.cpp
+++ b/contrib/catch2/src/catch2/catch_tostring.cpp
diff --git a/src/catch2/catch_tostring.hpp b/contrib/catch2/src/catch2/catch_tostring.hpp
index 5f84196..5f84196 100644
--- a/src/catch2/catch_tostring.hpp
+++ b/contrib/catch2/src/catch2/catch_tostring.hpp
diff --git a/src/catch2/catch_totals.cpp b/contrib/catch2/src/catch2/catch_totals.cpp
index bd1954f..bd1954f 100644
--- a/src/catch2/catch_totals.cpp
+++ b/contrib/catch2/src/catch2/catch_totals.cpp
diff --git a/src/catch2/catch_totals.hpp b/contrib/catch2/src/catch2/catch_totals.hpp
index 386392c..386392c 100644
--- a/src/catch2/catch_totals.hpp
+++ b/contrib/catch2/src/catch2/catch_totals.hpp
diff --git a/src/catch2/catch_translate_exception.cpp b/contrib/catch2/src/catch2/catch_translate_exception.cpp
index c4b2894..c4b2894 100644
--- a/src/catch2/catch_translate_exception.cpp
+++ b/contrib/catch2/src/catch2/catch_translate_exception.cpp
diff --git a/src/catch2/catch_translate_exception.hpp b/contrib/catch2/src/catch2/catch_translate_exception.hpp
index 2bf8d36..2bf8d36 100644
--- a/src/catch2/catch_translate_exception.hpp
+++ b/contrib/catch2/src/catch2/catch_translate_exception.hpp
diff --git a/src/catch2/catch_user_config.hpp.in b/contrib/catch2/src/catch2/catch_user_config.hpp.in
index 3acda68..3acda68 100644
--- a/src/catch2/catch_user_config.hpp.in
+++ b/contrib/catch2/src/catch2/catch_user_config.hpp.in
diff --git a/src/catch2/catch_version.cpp b/contrib/catch2/src/catch2/catch_version.cpp
index 2c40ccc..2c40ccc 100644
--- a/src/catch2/catch_version.cpp
+++ b/contrib/catch2/src/catch2/catch_version.cpp
diff --git a/src/catch2/catch_version.hpp b/contrib/catch2/src/catch2/catch_version.hpp
index af698fa..af698fa 100644
--- a/src/catch2/catch_version.hpp
+++ b/contrib/catch2/src/catch2/catch_version.hpp
diff --git a/src/catch2/catch_version_macros.hpp b/contrib/catch2/src/catch2/catch_version_macros.hpp
index 42655fa..42655fa 100644
--- a/src/catch2/catch_version_macros.hpp
+++ b/contrib/catch2/src/catch2/catch_version_macros.hpp
diff --git a/src/catch2/generators/catch_generator_exception.cpp b/contrib/catch2/src/catch2/generators/catch_generator_exception.cpp
index 6432403..6432403 100644
--- a/src/catch2/generators/catch_generator_exception.cpp
+++ b/contrib/catch2/src/catch2/generators/catch_generator_exception.cpp
diff --git a/src/catch2/generators/catch_generator_exception.hpp b/contrib/catch2/src/catch2/generators/catch_generator_exception.hpp
index f353042..f353042 100644
--- a/src/catch2/generators/catch_generator_exception.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generator_exception.hpp
diff --git a/src/catch2/generators/catch_generators.cpp b/contrib/catch2/src/catch2/generators/catch_generators.cpp
index 3514e9f..3514e9f 100644
--- a/src/catch2/generators/catch_generators.cpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators.cpp
diff --git a/src/catch2/generators/catch_generators.hpp b/contrib/catch2/src/catch2/generators/catch_generators.hpp
index 0f35a99..0f35a99 100644
--- a/src/catch2/generators/catch_generators.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators.hpp
diff --git a/src/catch2/generators/catch_generators_adapters.hpp b/contrib/catch2/src/catch2/generators/catch_generators_adapters.hpp
index d5fc1e1..d5fc1e1 100644
--- a/src/catch2/generators/catch_generators_adapters.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators_adapters.hpp
diff --git a/src/catch2/generators/catch_generators_all.hpp b/contrib/catch2/src/catch2/generators/catch_generators_all.hpp
index c12d314..c12d314 100644
--- a/src/catch2/generators/catch_generators_all.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators_all.hpp
diff --git a/src/catch2/generators/catch_generators_random.cpp b/contrib/catch2/src/catch2/generators/catch_generators_random.cpp
index 00a8e63..00a8e63 100644
--- a/src/catch2/generators/catch_generators_random.cpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators_random.cpp
diff --git a/src/catch2/generators/catch_generators_random.hpp b/contrib/catch2/src/catch2/generators/catch_generators_random.hpp
index 7128356..7128356 100644
--- a/src/catch2/generators/catch_generators_random.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators_random.hpp
diff --git a/src/catch2/generators/catch_generators_range.hpp b/contrib/catch2/src/catch2/generators/catch_generators_range.hpp
index 55d673c..55d673c 100644
--- a/src/catch2/generators/catch_generators_range.hpp
+++ b/contrib/catch2/src/catch2/generators/catch_generators_range.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_all.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_all.hpp
index a99fdcd..a99fdcd 100644
--- a/src/catch2/interfaces/catch_interfaces_all.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_all.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_capture.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.cpp
index 9b40ee5..9b40ee5 100644
--- a/src/catch2/interfaces/catch_interfaces_capture.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_capture.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.hpp
index 9e5431d..9e5431d 100644
--- a/src/catch2/interfaces/catch_interfaces_capture.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_capture.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_config.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_config.cpp
index 655bc1b..655bc1b 100644
--- a/src/catch2/interfaces/catch_interfaces_config.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_config.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_config.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_config.hpp
index eb05480..eb05480 100644
--- a/src/catch2/interfaces/catch_interfaces_config.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_config.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp
index 38b052d..38b052d 100644
--- a/src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_enum_values_registry.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_exception.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.cpp
index 44c272d..44c272d 100644
--- a/src/catch2/interfaces/catch_interfaces_exception.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_exception.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.hpp
index fcc2a8f..fcc2a8f 100644
--- a/src/catch2/interfaces/catch_interfaces_exception.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_exception.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_generatortracker.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.cpp
index e9fa5dd..e9fa5dd 100644
--- a/src/catch2/interfaces/catch_interfaces_generatortracker.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_generatortracker.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.hpp
index d70cb59..d70cb59 100644
--- a/src/catch2/interfaces/catch_interfaces_generatortracker.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_generatortracker.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_registry_hub.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.cpp
index cd688a4..cd688a4 100644
--- a/src/catch2/interfaces/catch_interfaces_registry_hub.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_registry_hub.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.hpp
index 113f223..113f223 100644
--- a/src/catch2/interfaces/catch_interfaces_registry_hub.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_registry_hub.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_reporter.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.cpp
index 90536bb..90536bb 100644
--- a/src/catch2/interfaces/catch_interfaces_reporter.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_reporter.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.hpp
index a052c5d..a052c5d 100644
--- a/src/catch2/interfaces/catch_interfaces_reporter.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_reporter_factory.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.cpp
index 4fe992f..4fe992f 100644
--- a/src/catch2/interfaces/catch_interfaces_reporter_factory.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_reporter_factory.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.hpp
index 83ddd7b..83ddd7b 100644
--- a/src/catch2/interfaces/catch_interfaces_reporter_factory.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_reporter_factory.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp
index 5da0f8d..5da0f8d 100644
--- a/src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_tag_alias_registry.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_test_invoker.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_test_invoker.hpp
index 124a7f7..124a7f7 100644
--- a/src/catch2/interfaces/catch_interfaces_test_invoker.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_test_invoker.hpp
diff --git a/src/catch2/interfaces/catch_interfaces_testcase.cpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.cpp
index a543116..a543116 100644
--- a/src/catch2/interfaces/catch_interfaces_testcase.cpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.cpp
diff --git a/src/catch2/interfaces/catch_interfaces_testcase.hpp b/contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.hpp
index daee848..daee848 100644
--- a/src/catch2/interfaces/catch_interfaces_testcase.hpp
+++ b/contrib/catch2/src/catch2/interfaces/catch_interfaces_testcase.hpp
diff --git a/src/catch2/internal/catch_assertion_handler.cpp b/contrib/catch2/src/catch2/internal/catch_assertion_handler.cpp
index 9a28e79..9a28e79 100644
--- a/src/catch2/internal/catch_assertion_handler.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_assertion_handler.cpp
diff --git a/src/catch2/internal/catch_assertion_handler.hpp b/contrib/catch2/src/catch2/internal/catch_assertion_handler.hpp
index c71c689..c71c689 100644
--- a/src/catch2/internal/catch_assertion_handler.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_assertion_handler.hpp
diff --git a/src/catch2/internal/catch_case_insensitive_comparisons.cpp b/contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.cpp
index b3e7b53..b3e7b53 100644
--- a/src/catch2/internal/catch_case_insensitive_comparisons.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.cpp
diff --git a/src/catch2/internal/catch_case_insensitive_comparisons.hpp b/contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.hpp
index 33b7782..33b7782 100644
--- a/src/catch2/internal/catch_case_insensitive_comparisons.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_case_insensitive_comparisons.hpp
diff --git a/src/catch2/internal/catch_clara.cpp b/contrib/catch2/src/catch2/internal/catch_clara.cpp
index f9dd913..f9dd913 100644
--- a/src/catch2/internal/catch_clara.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_clara.cpp
diff --git a/src/catch2/internal/catch_clara.hpp b/contrib/catch2/src/catch2/internal/catch_clara.hpp
index d869593..d869593 100644
--- a/src/catch2/internal/catch_clara.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_clara.hpp
diff --git a/src/catch2/internal/catch_commandline.cpp b/contrib/catch2/src/catch2/internal/catch_commandline.cpp
index 212f177..212f177 100644
--- a/src/catch2/internal/catch_commandline.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_commandline.cpp
diff --git a/src/catch2/internal/catch_commandline.hpp b/contrib/catch2/src/catch2/internal/catch_commandline.hpp
index 8cc2254..8cc2254 100644
--- a/src/catch2/internal/catch_commandline.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_commandline.hpp
diff --git a/src/catch2/internal/catch_compare_traits.hpp b/contrib/catch2/src/catch2/internal/catch_compare_traits.hpp
index 6304b1f..6304b1f 100644
--- a/src/catch2/internal/catch_compare_traits.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_compare_traits.hpp
diff --git a/src/catch2/internal/catch_compiler_capabilities.hpp b/contrib/catch2/src/catch2/internal/catch_compiler_capabilities.hpp
index 4cf1d65..4cf1d65 100644
--- a/src/catch2/internal/catch_compiler_capabilities.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_compiler_capabilities.hpp
diff --git a/src/catch2/internal/catch_config_android_logwrite.hpp b/contrib/catch2/src/catch2/internal/catch_config_android_logwrite.hpp
index 490ee37..490ee37 100644
--- a/src/catch2/internal/catch_config_android_logwrite.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_android_logwrite.hpp
diff --git a/src/catch2/internal/catch_config_counter.hpp b/contrib/catch2/src/catch2/internal/catch_config_counter.hpp
index a482ce3..a482ce3 100644
--- a/src/catch2/internal/catch_config_counter.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_counter.hpp
diff --git a/src/catch2/internal/catch_config_prefix_messages.hpp b/contrib/catch2/src/catch2/internal/catch_config_prefix_messages.hpp
index be1e9a9..be1e9a9 100644
--- a/src/catch2/internal/catch_config_prefix_messages.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_prefix_messages.hpp
diff --git a/src/catch2/internal/catch_config_static_analysis_support.hpp b/contrib/catch2/src/catch2/internal/catch_config_static_analysis_support.hpp
index 81bdf39..81bdf39 100644
--- a/src/catch2/internal/catch_config_static_analysis_support.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_static_analysis_support.hpp
diff --git a/src/catch2/internal/catch_config_uncaught_exceptions.hpp b/contrib/catch2/src/catch2/internal/catch_config_uncaught_exceptions.hpp
index 20b1dfc..20b1dfc 100644
--- a/src/catch2/internal/catch_config_uncaught_exceptions.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_uncaught_exceptions.hpp
diff --git a/src/catch2/internal/catch_config_wchar.hpp b/contrib/catch2/src/catch2/internal/catch_config_wchar.hpp
index 90d85d0..90d85d0 100644
--- a/src/catch2/internal/catch_config_wchar.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_config_wchar.hpp
diff --git a/src/catch2/internal/catch_console_colour.cpp b/contrib/catch2/src/catch2/internal/catch_console_colour.cpp
index 142de9e..142de9e 100644
--- a/src/catch2/internal/catch_console_colour.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_console_colour.cpp
diff --git a/src/catch2/internal/catch_console_colour.hpp b/contrib/catch2/src/catch2/internal/catch_console_colour.hpp
index d914431..d914431 100644
--- a/src/catch2/internal/catch_console_colour.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_console_colour.hpp
diff --git a/src/catch2/internal/catch_console_width.hpp b/contrib/catch2/src/catch2/internal/catch_console_width.hpp
index 1655361..1655361 100644
--- a/src/catch2/internal/catch_console_width.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_console_width.hpp
diff --git a/src/catch2/internal/catch_container_nonmembers.hpp b/contrib/catch2/src/catch2/internal/catch_container_nonmembers.hpp
index 33b28a9..33b28a9 100644
--- a/src/catch2/internal/catch_container_nonmembers.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_container_nonmembers.hpp
diff --git a/src/catch2/internal/catch_context.cpp b/contrib/catch2/src/catch2/internal/catch_context.cpp
index 8acf1ed..8acf1ed 100644
--- a/src/catch2/internal/catch_context.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_context.cpp
diff --git a/src/catch2/internal/catch_context.hpp b/contrib/catch2/src/catch2/internal/catch_context.hpp
index 4d8a5da..4d8a5da 100644
--- a/src/catch2/internal/catch_context.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_context.hpp
diff --git a/src/catch2/internal/catch_debug_console.cpp b/contrib/catch2/src/catch2/internal/catch_debug_console.cpp
index 40dd0a6..40dd0a6 100644
--- a/src/catch2/internal/catch_debug_console.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_debug_console.cpp
diff --git a/src/catch2/internal/catch_debug_console.hpp b/contrib/catch2/src/catch2/internal/catch_debug_console.hpp
index 8784f78..8784f78 100644
--- a/src/catch2/internal/catch_debug_console.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_debug_console.hpp
diff --git a/src/catch2/internal/catch_debugger.cpp b/contrib/catch2/src/catch2/internal/catch_debugger.cpp
index bd3be17..bd3be17 100644
--- a/src/catch2/internal/catch_debugger.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_debugger.cpp
diff --git a/src/catch2/internal/catch_debugger.hpp b/contrib/catch2/src/catch2/internal/catch_debugger.hpp
index 25c5a26..25c5a26 100644
--- a/src/catch2/internal/catch_debugger.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_debugger.hpp
diff --git a/src/catch2/internal/catch_decomposer.cpp b/contrib/catch2/src/catch2/internal/catch_decomposer.cpp
index 17a7bc9..17a7bc9 100644
--- a/src/catch2/internal/catch_decomposer.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_decomposer.cpp
diff --git a/src/catch2/internal/catch_decomposer.hpp b/contrib/catch2/src/catch2/internal/catch_decomposer.hpp
index adce89f..adce89f 100644
--- a/src/catch2/internal/catch_decomposer.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_decomposer.hpp
diff --git a/src/catch2/internal/catch_enforce.cpp b/contrib/catch2/src/catch2/internal/catch_enforce.cpp
index 3f69640..3f69640 100644
--- a/src/catch2/internal/catch_enforce.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_enforce.cpp
diff --git a/src/catch2/internal/catch_enforce.hpp b/contrib/catch2/src/catch2/internal/catch_enforce.hpp
index 076cea3..076cea3 100644
--- a/src/catch2/internal/catch_enforce.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_enforce.hpp
diff --git a/src/catch2/internal/catch_enum_values_registry.cpp b/contrib/catch2/src/catch2/internal/catch_enum_values_registry.cpp
index a94b608..a94b608 100644
--- a/src/catch2/internal/catch_enum_values_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_enum_values_registry.cpp
diff --git a/src/catch2/internal/catch_enum_values_registry.hpp b/contrib/catch2/src/catch2/internal/catch_enum_values_registry.hpp
index de994c3..de994c3 100644
--- a/src/catch2/internal/catch_enum_values_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_enum_values_registry.hpp
diff --git a/src/catch2/internal/catch_errno_guard.cpp b/contrib/catch2/src/catch2/internal/catch_errno_guard.cpp
index 3bbf8b4..3bbf8b4 100644
--- a/src/catch2/internal/catch_errno_guard.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_errno_guard.cpp
diff --git a/src/catch2/internal/catch_errno_guard.hpp b/contrib/catch2/src/catch2/internal/catch_errno_guard.hpp
index df1237d..df1237d 100644
--- a/src/catch2/internal/catch_errno_guard.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_errno_guard.hpp
diff --git a/src/catch2/internal/catch_exception_translator_registry.cpp b/contrib/catch2/src/catch2/internal/catch_exception_translator_registry.cpp
index 1eb6114..1eb6114 100644
--- a/src/catch2/internal/catch_exception_translator_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_exception_translator_registry.cpp
diff --git a/src/catch2/internal/catch_exception_translator_registry.hpp b/contrib/catch2/src/catch2/internal/catch_exception_translator_registry.hpp
index 3123e93..3123e93 100644
--- a/src/catch2/internal/catch_exception_translator_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_exception_translator_registry.hpp
diff --git a/src/catch2/internal/catch_fatal_condition_handler.cpp b/contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.cpp
index 9ef5b21..9ef5b21 100644
--- a/src/catch2/internal/catch_fatal_condition_handler.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.cpp
diff --git a/src/catch2/internal/catch_fatal_condition_handler.hpp b/contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.hpp
index 81728b5..81728b5 100644
--- a/src/catch2/internal/catch_fatal_condition_handler.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_fatal_condition_handler.hpp
diff --git a/src/catch2/internal/catch_floating_point_helpers.cpp b/contrib/catch2/src/catch2/internal/catch_floating_point_helpers.cpp
index 9631ed6..9631ed6 100644
--- a/src/catch2/internal/catch_floating_point_helpers.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_floating_point_helpers.cpp
diff --git a/src/catch2/internal/catch_floating_point_helpers.hpp b/contrib/catch2/src/catch2/internal/catch_floating_point_helpers.hpp
index b214372..b214372 100644
--- a/src/catch2/internal/catch_floating_point_helpers.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_floating_point_helpers.hpp
diff --git a/src/catch2/internal/catch_getenv.cpp b/contrib/catch2/src/catch2/internal/catch_getenv.cpp
index a9a592c..a9a592c 100644
--- a/src/catch2/internal/catch_getenv.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_getenv.cpp
diff --git a/src/catch2/internal/catch_getenv.hpp b/contrib/catch2/src/catch2/internal/catch_getenv.hpp
index 31ef797..31ef797 100644
--- a/src/catch2/internal/catch_getenv.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_getenv.hpp
diff --git a/src/catch2/internal/catch_is_permutation.hpp b/contrib/catch2/src/catch2/internal/catch_is_permutation.hpp
index c77a6d3..c77a6d3 100644
--- a/src/catch2/internal/catch_is_permutation.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_is_permutation.hpp
diff --git a/src/catch2/internal/catch_istream.cpp b/contrib/catch2/src/catch2/internal/catch_istream.cpp
index 2867ce7..2867ce7 100644
--- a/src/catch2/internal/catch_istream.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_istream.cpp
diff --git a/src/catch2/internal/catch_istream.hpp b/contrib/catch2/src/catch2/internal/catch_istream.hpp
index e6b9a2d..e6b9a2d 100644
--- a/src/catch2/internal/catch_istream.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_istream.hpp
diff --git a/src/catch2/internal/catch_jsonwriter.cpp b/contrib/catch2/src/catch2/internal/catch_jsonwriter.cpp
index 1a96e34..1a96e34 100644
--- a/src/catch2/internal/catch_jsonwriter.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_jsonwriter.cpp
diff --git a/src/catch2/internal/catch_jsonwriter.hpp b/contrib/catch2/src/catch2/internal/catch_jsonwriter.hpp
index 23b56d1..23b56d1 100644
--- a/src/catch2/internal/catch_jsonwriter.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_jsonwriter.hpp
diff --git a/src/catch2/internal/catch_lazy_expr.cpp b/contrib/catch2/src/catch2/internal/catch_lazy_expr.cpp
index 56a5ae5..56a5ae5 100644
--- a/src/catch2/internal/catch_lazy_expr.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_lazy_expr.cpp
diff --git a/src/catch2/internal/catch_lazy_expr.hpp b/contrib/catch2/src/catch2/internal/catch_lazy_expr.hpp
index c6ff224..c6ff224 100644
--- a/src/catch2/internal/catch_lazy_expr.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_lazy_expr.hpp
diff --git a/src/catch2/internal/catch_leak_detector.cpp b/contrib/catch2/src/catch2/internal/catch_leak_detector.cpp
index 691bc77..691bc77 100644
--- a/src/catch2/internal/catch_leak_detector.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_leak_detector.cpp
diff --git a/src/catch2/internal/catch_leak_detector.hpp b/contrib/catch2/src/catch2/internal/catch_leak_detector.hpp
index 94c8f32..94c8f32 100644
--- a/src/catch2/internal/catch_leak_detector.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_leak_detector.hpp
diff --git a/src/catch2/internal/catch_list.cpp b/contrib/catch2/src/catch2/internal/catch_list.cpp
index 5bd06a2..5bd06a2 100644
--- a/src/catch2/internal/catch_list.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_list.cpp
diff --git a/src/catch2/internal/catch_list.hpp b/contrib/catch2/src/catch2/internal/catch_list.hpp
index 9b4abd8..9b4abd8 100644
--- a/src/catch2/internal/catch_list.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_list.hpp
diff --git a/src/catch2/internal/catch_logical_traits.hpp b/contrib/catch2/src/catch2/internal/catch_logical_traits.hpp
index bd87565..bd87565 100644
--- a/src/catch2/internal/catch_logical_traits.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_logical_traits.hpp
diff --git a/src/catch2/internal/catch_main.cpp b/contrib/catch2/src/catch2/internal/catch_main.cpp
index 503b540..503b540 100644
--- a/src/catch2/internal/catch_main.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_main.cpp
diff --git a/src/catch2/internal/catch_message_info.cpp b/contrib/catch2/src/catch2/internal/catch_message_info.cpp
index e0e9dc7..e0e9dc7 100644
--- a/src/catch2/internal/catch_message_info.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_message_info.cpp
diff --git a/src/catch2/internal/catch_message_info.hpp b/contrib/catch2/src/catch2/internal/catch_message_info.hpp
index 1ef43fd..1ef43fd 100644
--- a/src/catch2/internal/catch_message_info.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_message_info.hpp
diff --git a/src/catch2/internal/catch_meta.hpp b/contrib/catch2/src/catch2/internal/catch_meta.hpp
index 6fbc13a..6fbc13a 100644
--- a/src/catch2/internal/catch_meta.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_meta.hpp
diff --git a/src/catch2/internal/catch_move_and_forward.hpp b/contrib/catch2/src/catch2/internal/catch_move_and_forward.hpp
index 383d85c..383d85c 100644
--- a/src/catch2/internal/catch_move_and_forward.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_move_and_forward.hpp
diff --git a/src/catch2/internal/catch_noncopyable.hpp b/contrib/catch2/src/catch2/internal/catch_noncopyable.hpp
index eb0823e..eb0823e 100644
--- a/src/catch2/internal/catch_noncopyable.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_noncopyable.hpp
diff --git a/src/catch2/internal/catch_optional.hpp b/contrib/catch2/src/catch2/internal/catch_optional.hpp
index d1e953a..d1e953a 100644
--- a/src/catch2/internal/catch_optional.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_optional.hpp
diff --git a/src/catch2/internal/catch_output_redirect.cpp b/contrib/catch2/src/catch2/internal/catch_output_redirect.cpp
index 245e137..245e137 100644
--- a/src/catch2/internal/catch_output_redirect.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_output_redirect.cpp
diff --git a/src/catch2/internal/catch_output_redirect.hpp b/contrib/catch2/src/catch2/internal/catch_output_redirect.hpp
index 51b796b..51b796b 100644
--- a/src/catch2/internal/catch_output_redirect.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_output_redirect.hpp
diff --git a/src/catch2/internal/catch_parse_numbers.cpp b/contrib/catch2/src/catch2/internal/catch_parse_numbers.cpp
index d949ac1..d949ac1 100644
--- a/src/catch2/internal/catch_parse_numbers.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_parse_numbers.cpp
diff --git a/src/catch2/internal/catch_parse_numbers.hpp b/contrib/catch2/src/catch2/internal/catch_parse_numbers.hpp
index 3dabf95..3dabf95 100644
--- a/src/catch2/internal/catch_parse_numbers.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_parse_numbers.hpp
diff --git a/src/catch2/internal/catch_platform.hpp b/contrib/catch2/src/catch2/internal/catch_platform.hpp
index b653a58..b653a58 100644
--- a/src/catch2/internal/catch_platform.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_platform.hpp
diff --git a/src/catch2/internal/catch_polyfills.cpp b/contrib/catch2/src/catch2/internal/catch_polyfills.cpp
index 776c224..776c224 100644
--- a/src/catch2/internal/catch_polyfills.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_polyfills.cpp
diff --git a/src/catch2/internal/catch_polyfills.hpp b/contrib/catch2/src/catch2/internal/catch_polyfills.hpp
index 4503f8f..4503f8f 100644
--- a/src/catch2/internal/catch_polyfills.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_polyfills.hpp
diff --git a/src/catch2/internal/catch_preprocessor.hpp b/contrib/catch2/src/catch2/internal/catch_preprocessor.hpp
index 08e844d..08e844d 100644
--- a/src/catch2/internal/catch_preprocessor.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_preprocessor.hpp
diff --git a/src/catch2/internal/catch_preprocessor_internal_stringify.hpp b/contrib/catch2/src/catch2/internal/catch_preprocessor_internal_stringify.hpp
index 2fd64e1..2fd64e1 100644
--- a/src/catch2/internal/catch_preprocessor_internal_stringify.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_preprocessor_internal_stringify.hpp
diff --git a/src/catch2/internal/catch_preprocessor_remove_parens.hpp b/contrib/catch2/src/catch2/internal/catch_preprocessor_remove_parens.hpp
index dee11cd..dee11cd 100644
--- a/src/catch2/internal/catch_preprocessor_remove_parens.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_preprocessor_remove_parens.hpp
diff --git a/src/catch2/internal/catch_random_floating_point_helpers.hpp b/contrib/catch2/src/catch2/internal/catch_random_floating_point_helpers.hpp
index c59c053..c59c053 100644
--- a/src/catch2/internal/catch_random_floating_point_helpers.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_floating_point_helpers.hpp
diff --git a/src/catch2/internal/catch_random_integer_helpers.hpp b/contrib/catch2/src/catch2/internal/catch_random_integer_helpers.hpp
index be4bbe9..be4bbe9 100644
--- a/src/catch2/internal/catch_random_integer_helpers.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_integer_helpers.hpp
diff --git a/src/catch2/internal/catch_random_number_generator.cpp b/contrib/catch2/src/catch2/internal/catch_random_number_generator.cpp
index c88cd8f..c88cd8f 100644
--- a/src/catch2/internal/catch_random_number_generator.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_number_generator.cpp
diff --git a/src/catch2/internal/catch_random_number_generator.hpp b/contrib/catch2/src/catch2/internal/catch_random_number_generator.hpp
index e4129be..e4129be 100644
--- a/src/catch2/internal/catch_random_number_generator.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_number_generator.hpp
diff --git a/src/catch2/internal/catch_random_seed_generation.cpp b/contrib/catch2/src/catch2/internal/catch_random_seed_generation.cpp
index fdc3fa1..fdc3fa1 100644
--- a/src/catch2/internal/catch_random_seed_generation.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_seed_generation.cpp
diff --git a/src/catch2/internal/catch_random_seed_generation.hpp b/contrib/catch2/src/catch2/internal/catch_random_seed_generation.hpp
index d0d6fb2..d0d6fb2 100644
--- a/src/catch2/internal/catch_random_seed_generation.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_random_seed_generation.hpp
diff --git a/src/catch2/internal/catch_reporter_registry.cpp b/contrib/catch2/src/catch2/internal/catch_reporter_registry.cpp
index cea8c4d..cea8c4d 100644
--- a/src/catch2/internal/catch_reporter_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_reporter_registry.cpp
diff --git a/src/catch2/internal/catch_reporter_registry.hpp b/contrib/catch2/src/catch2/internal/catch_reporter_registry.hpp
index 92a8892..92a8892 100644
--- a/src/catch2/internal/catch_reporter_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_reporter_registry.hpp
diff --git a/src/catch2/internal/catch_reporter_spec_parser.cpp b/contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.cpp
index 2b08758..2b08758 100644
--- a/src/catch2/internal/catch_reporter_spec_parser.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.cpp
diff --git a/src/catch2/internal/catch_reporter_spec_parser.hpp b/contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.hpp
index 9f447ee..9f447ee 100644
--- a/src/catch2/internal/catch_reporter_spec_parser.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_reporter_spec_parser.hpp
diff --git a/src/catch2/internal/catch_result_type.hpp b/contrib/catch2/src/catch2/internal/catch_result_type.hpp
index 69a6ef1..69a6ef1 100644
--- a/src/catch2/internal/catch_result_type.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_result_type.hpp
diff --git a/src/catch2/internal/catch_reusable_string_stream.cpp b/contrib/catch2/src/catch2/internal/catch_reusable_string_stream.cpp
index 33eafde..33eafde 100644
--- a/src/catch2/internal/catch_reusable_string_stream.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_reusable_string_stream.cpp
diff --git a/src/catch2/internal/catch_reusable_string_stream.hpp b/contrib/catch2/src/catch2/internal/catch_reusable_string_stream.hpp
index 5b864f3..5b864f3 100644
--- a/src/catch2/internal/catch_reusable_string_stream.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_reusable_string_stream.hpp
diff --git a/src/catch2/internal/catch_run_context.cpp b/contrib/catch2/src/catch2/internal/catch_run_context.cpp
index 2a102fb..2a102fb 100644
--- a/src/catch2/internal/catch_run_context.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_run_context.cpp
diff --git a/src/catch2/internal/catch_run_context.hpp b/contrib/catch2/src/catch2/internal/catch_run_context.hpp
index c66fec0..c66fec0 100644
--- a/src/catch2/internal/catch_run_context.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_run_context.hpp
diff --git a/src/catch2/internal/catch_section.cpp b/contrib/catch2/src/catch2/internal/catch_section.cpp
index 677c216..677c216 100644
--- a/src/catch2/internal/catch_section.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_section.cpp
diff --git a/src/catch2/internal/catch_section.hpp b/contrib/catch2/src/catch2/internal/catch_section.hpp
index e56c79f..e56c79f 100644
--- a/src/catch2/internal/catch_section.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_section.hpp
diff --git a/src/catch2/internal/catch_sharding.hpp b/contrib/catch2/src/catch2/internal/catch_sharding.hpp
index 22561f4..22561f4 100644
--- a/src/catch2/internal/catch_sharding.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_sharding.hpp
diff --git a/src/catch2/internal/catch_singletons.cpp b/contrib/catch2/src/catch2/internal/catch_singletons.cpp
index 4e856de..4e856de 100644
--- a/src/catch2/internal/catch_singletons.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_singletons.cpp
diff --git a/src/catch2/internal/catch_singletons.hpp b/contrib/catch2/src/catch2/internal/catch_singletons.hpp
index a28a13d..a28a13d 100644
--- a/src/catch2/internal/catch_singletons.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_singletons.hpp
diff --git a/src/catch2/internal/catch_source_line_info.cpp b/contrib/catch2/src/catch2/internal/catch_source_line_info.cpp
index f55f1c9..f55f1c9 100644
--- a/src/catch2/internal/catch_source_line_info.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_source_line_info.cpp
diff --git a/src/catch2/internal/catch_source_line_info.hpp b/contrib/catch2/src/catch2/internal/catch_source_line_info.hpp
index c598052..c598052 100644
--- a/src/catch2/internal/catch_source_line_info.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_source_line_info.hpp
diff --git a/src/catch2/internal/catch_startup_exception_registry.cpp b/contrib/catch2/src/catch2/internal/catch_startup_exception_registry.cpp
index 1607663..1607663 100644
--- a/src/catch2/internal/catch_startup_exception_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_startup_exception_registry.cpp
diff --git a/src/catch2/internal/catch_startup_exception_registry.hpp b/contrib/catch2/src/catch2/internal/catch_startup_exception_registry.hpp
index aef4667..aef4667 100644
--- a/src/catch2/internal/catch_startup_exception_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_startup_exception_registry.hpp
diff --git a/src/catch2/internal/catch_stdstreams.cpp b/contrib/catch2/src/catch2/internal/catch_stdstreams.cpp
index a4502b2..a4502b2 100644
--- a/src/catch2/internal/catch_stdstreams.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_stdstreams.cpp
diff --git a/src/catch2/internal/catch_stdstreams.hpp b/contrib/catch2/src/catch2/internal/catch_stdstreams.hpp
index 02aec63..02aec63 100644
--- a/src/catch2/internal/catch_stdstreams.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_stdstreams.hpp
diff --git a/src/catch2/internal/catch_stream_end_stop.hpp b/contrib/catch2/src/catch2/internal/catch_stream_end_stop.hpp
index 66d678c..66d678c 100644
--- a/src/catch2/internal/catch_stream_end_stop.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_stream_end_stop.hpp
diff --git a/src/catch2/internal/catch_string_manip.cpp b/contrib/catch2/src/catch2/internal/catch_string_manip.cpp
index ce1abaa..ce1abaa 100644
--- a/src/catch2/internal/catch_string_manip.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_string_manip.cpp
diff --git a/src/catch2/internal/catch_string_manip.hpp b/contrib/catch2/src/catch2/internal/catch_string_manip.hpp
index dc0c552..dc0c552 100644
--- a/src/catch2/internal/catch_string_manip.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_string_manip.hpp
diff --git a/src/catch2/internal/catch_stringref.cpp b/contrib/catch2/src/catch2/internal/catch_stringref.cpp
index 232498e..232498e 100644
--- a/src/catch2/internal/catch_stringref.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_stringref.cpp
diff --git a/src/catch2/internal/catch_stringref.hpp b/contrib/catch2/src/catch2/internal/catch_stringref.hpp
index 421ce71..421ce71 100644
--- a/src/catch2/internal/catch_stringref.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_stringref.hpp
diff --git a/src/catch2/internal/catch_tag_alias_registry.cpp b/contrib/catch2/src/catch2/internal/catch_tag_alias_registry.cpp
index 510df16..510df16 100644
--- a/src/catch2/internal/catch_tag_alias_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_tag_alias_registry.cpp
diff --git a/src/catch2/internal/catch_tag_alias_registry.hpp b/contrib/catch2/src/catch2/internal/catch_tag_alias_registry.hpp
index 64c0f8f..64c0f8f 100644
--- a/src/catch2/internal/catch_tag_alias_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_tag_alias_registry.hpp
diff --git a/src/catch2/internal/catch_template_test_registry.hpp b/contrib/catch2/src/catch2/internal/catch_template_test_registry.hpp
index 0ea354b..0ea354b 100644
--- a/src/catch2/internal/catch_template_test_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_template_test_registry.hpp
diff --git a/src/catch2/internal/catch_test_case_info_hasher.cpp b/contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.cpp
index e1731eb..e1731eb 100644
--- a/src/catch2/internal/catch_test_case_info_hasher.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.cpp
diff --git a/src/catch2/internal/catch_test_case_info_hasher.hpp b/contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.hpp
index b0422dc..b0422dc 100644
--- a/src/catch2/internal/catch_test_case_info_hasher.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_info_hasher.hpp
diff --git a/src/catch2/internal/catch_test_case_registry_impl.cpp b/contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.cpp
index e77e7bc..e77e7bc 100644
--- a/src/catch2/internal/catch_test_case_registry_impl.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.cpp
diff --git a/src/catch2/internal/catch_test_case_registry_impl.hpp b/contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.hpp
index fbca89f..fbca89f 100644
--- a/src/catch2/internal/catch_test_case_registry_impl.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_registry_impl.hpp
diff --git a/src/catch2/internal/catch_test_case_tracker.cpp b/contrib/catch2/src/catch2/internal/catch_test_case_tracker.cpp
index 1470b91..1470b91 100644
--- a/src/catch2/internal/catch_test_case_tracker.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_tracker.cpp
diff --git a/src/catch2/internal/catch_test_case_tracker.hpp b/contrib/catch2/src/catch2/internal/catch_test_case_tracker.hpp
index 50278c9..50278c9 100644
--- a/src/catch2/internal/catch_test_case_tracker.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_case_tracker.hpp
diff --git a/src/catch2/internal/catch_test_failure_exception.cpp b/contrib/catch2/src/catch2/internal/catch_test_failure_exception.cpp
index 8ea3131..8ea3131 100644
--- a/src/catch2/internal/catch_test_failure_exception.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_failure_exception.cpp
diff --git a/src/catch2/internal/catch_test_failure_exception.hpp b/contrib/catch2/src/catch2/internal/catch_test_failure_exception.hpp
index 1ef8836..1ef8836 100644
--- a/src/catch2/internal/catch_test_failure_exception.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_failure_exception.hpp
diff --git a/src/catch2/internal/catch_test_macro_impl.hpp b/contrib/catch2/src/catch2/internal/catch_test_macro_impl.hpp
index ccd5bb3..ccd5bb3 100644
--- a/src/catch2/internal/catch_test_macro_impl.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_macro_impl.hpp
diff --git a/src/catch2/internal/catch_test_registry.cpp b/contrib/catch2/src/catch2/internal/catch_test_registry.cpp
index d017c50..d017c50 100644
--- a/src/catch2/internal/catch_test_registry.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_registry.cpp
diff --git a/src/catch2/internal/catch_test_registry.hpp b/contrib/catch2/src/catch2/internal/catch_test_registry.hpp
index 5c3a226..5c3a226 100644
--- a/src/catch2/internal/catch_test_registry.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_registry.hpp
diff --git a/src/catch2/internal/catch_test_run_info.hpp b/contrib/catch2/src/catch2/internal/catch_test_run_info.hpp
index 90357b0..90357b0 100644
--- a/src/catch2/internal/catch_test_run_info.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_run_info.hpp
diff --git a/src/catch2/internal/catch_test_spec_parser.cpp b/contrib/catch2/src/catch2/internal/catch_test_spec_parser.cpp
index d6e4cb5..d6e4cb5 100644
--- a/src/catch2/internal/catch_test_spec_parser.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_spec_parser.cpp
diff --git a/src/catch2/internal/catch_test_spec_parser.hpp b/contrib/catch2/src/catch2/internal/catch_test_spec_parser.hpp
index aa2917d..aa2917d 100644
--- a/src/catch2/internal/catch_test_spec_parser.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_test_spec_parser.hpp
diff --git a/src/catch2/internal/catch_textflow.cpp b/contrib/catch2/src/catch2/internal/catch_textflow.cpp
index 1c21d20..1c21d20 100644
--- a/src/catch2/internal/catch_textflow.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_textflow.cpp
diff --git a/src/catch2/internal/catch_textflow.hpp b/contrib/catch2/src/catch2/internal/catch_textflow.hpp
index 2d9d78a..2d9d78a 100644
--- a/src/catch2/internal/catch_textflow.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_textflow.hpp
diff --git a/src/catch2/internal/catch_to_string.hpp b/contrib/catch2/src/catch2/internal/catch_to_string.hpp
index c746216..c746216 100644
--- a/src/catch2/internal/catch_to_string.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_to_string.hpp
diff --git a/src/catch2/internal/catch_uncaught_exceptions.cpp b/contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.cpp
index 8cfabc0..8cfabc0 100644
--- a/src/catch2/internal/catch_uncaught_exceptions.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.cpp
diff --git a/src/catch2/internal/catch_uncaught_exceptions.hpp b/contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.hpp
index 8520864..8520864 100644
--- a/src/catch2/internal/catch_uncaught_exceptions.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_uncaught_exceptions.hpp
diff --git a/src/catch2/internal/catch_uniform_floating_point_distribution.hpp b/contrib/catch2/src/catch2/internal/catch_uniform_floating_point_distribution.hpp
index 23d03b4..23d03b4 100644
--- a/src/catch2/internal/catch_uniform_floating_point_distribution.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_uniform_floating_point_distribution.hpp
diff --git a/src/catch2/internal/catch_uniform_integer_distribution.hpp b/contrib/catch2/src/catch2/internal/catch_uniform_integer_distribution.hpp
index 799a93e..799a93e 100644
--- a/src/catch2/internal/catch_uniform_integer_distribution.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_uniform_integer_distribution.hpp
diff --git a/src/catch2/internal/catch_unique_name.hpp b/contrib/catch2/src/catch2/internal/catch_unique_name.hpp
index c6e1c2c..c6e1c2c 100644
--- a/src/catch2/internal/catch_unique_name.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_unique_name.hpp
diff --git a/src/catch2/internal/catch_unique_ptr.hpp b/contrib/catch2/src/catch2/internal/catch_unique_ptr.hpp
index 49cbc78..49cbc78 100644
--- a/src/catch2/internal/catch_unique_ptr.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_unique_ptr.hpp
diff --git a/src/catch2/internal/catch_void_type.hpp b/contrib/catch2/src/catch2/internal/catch_void_type.hpp
index dacc83d..dacc83d 100644
--- a/src/catch2/internal/catch_void_type.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_void_type.hpp
diff --git a/src/catch2/internal/catch_wildcard_pattern.cpp b/contrib/catch2/src/catch2/internal/catch_wildcard_pattern.cpp
index 09c5a40..09c5a40 100644
--- a/src/catch2/internal/catch_wildcard_pattern.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_wildcard_pattern.cpp
diff --git a/src/catch2/internal/catch_wildcard_pattern.hpp b/contrib/catch2/src/catch2/internal/catch_wildcard_pattern.hpp
index 4f41085..4f41085 100644
--- a/src/catch2/internal/catch_wildcard_pattern.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_wildcard_pattern.hpp
diff --git a/src/catch2/internal/catch_windows_h_proxy.hpp b/contrib/catch2/src/catch2/internal/catch_windows_h_proxy.hpp
index e3b9149..e3b9149 100644
--- a/src/catch2/internal/catch_windows_h_proxy.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_windows_h_proxy.hpp
diff --git a/src/catch2/internal/catch_xmlwriter.cpp b/contrib/catch2/src/catch2/internal/catch_xmlwriter.cpp
index ccf63a5..ccf63a5 100644
--- a/src/catch2/internal/catch_xmlwriter.cpp
+++ b/contrib/catch2/src/catch2/internal/catch_xmlwriter.cpp
diff --git a/src/catch2/internal/catch_xmlwriter.hpp b/contrib/catch2/src/catch2/internal/catch_xmlwriter.hpp
index 22b42c5..22b42c5 100644
--- a/src/catch2/internal/catch_xmlwriter.hpp
+++ b/contrib/catch2/src/catch2/internal/catch_xmlwriter.hpp
diff --git a/src/catch2/matchers/catch_matchers.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers.cpp
index 123b304..123b304 100644
--- a/src/catch2/matchers/catch_matchers.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers.cpp
diff --git a/src/catch2/matchers/catch_matchers.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers.hpp
index 3d996c3..3d996c3 100644
--- a/src/catch2/matchers/catch_matchers.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers.hpp
diff --git a/src/catch2/matchers/catch_matchers_all.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_all.hpp
index 83fe538..83fe538 100644
--- a/src/catch2/matchers/catch_matchers_all.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_all.hpp
diff --git a/src/catch2/matchers/catch_matchers_container_properties.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.cpp
index f0c535b..f0c535b 100644
--- a/src/catch2/matchers/catch_matchers_container_properties.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.cpp
diff --git a/src/catch2/matchers/catch_matchers_container_properties.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.hpp
index 5f3fc7f..5f3fc7f 100644
--- a/src/catch2/matchers/catch_matchers_container_properties.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_container_properties.hpp
diff --git a/src/catch2/matchers/catch_matchers_contains.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_contains.hpp
index 877d692..877d692 100644
--- a/src/catch2/matchers/catch_matchers_contains.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_contains.hpp
diff --git a/src/catch2/matchers/catch_matchers_exception.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_exception.cpp
index 8147390..8147390 100644
--- a/src/catch2/matchers/catch_matchers_exception.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_exception.cpp
diff --git a/src/catch2/matchers/catch_matchers_exception.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_exception.hpp
index e7c3a63..e7c3a63 100644
--- a/src/catch2/matchers/catch_matchers_exception.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_exception.hpp
diff --git a/src/catch2/matchers/catch_matchers_floating_point.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.cpp
index fc7b444..fc7b444 100644
--- a/src/catch2/matchers/catch_matchers_floating_point.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.cpp
diff --git a/src/catch2/matchers/catch_matchers_floating_point.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.hpp
index 7681663..7681663 100644
--- a/src/catch2/matchers/catch_matchers_floating_point.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_floating_point.hpp
diff --git a/src/catch2/matchers/catch_matchers_predicate.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_predicate.cpp
index f544537..f544537 100644
--- a/src/catch2/matchers/catch_matchers_predicate.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_predicate.cpp
diff --git a/src/catch2/matchers/catch_matchers_predicate.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_predicate.hpp
index 2d1cc33..2d1cc33 100644
--- a/src/catch2/matchers/catch_matchers_predicate.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_predicate.hpp
diff --git a/src/catch2/matchers/catch_matchers_quantifiers.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.cpp
index 5a75246..5a75246 100644
--- a/src/catch2/matchers/catch_matchers_quantifiers.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.cpp
diff --git a/src/catch2/matchers/catch_matchers_quantifiers.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.hpp
index 977b0c7..977b0c7 100644
--- a/src/catch2/matchers/catch_matchers_quantifiers.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_quantifiers.hpp
diff --git a/src/catch2/matchers/catch_matchers_range_equals.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_range_equals.hpp
index 8130f60..8130f60 100644
--- a/src/catch2/matchers/catch_matchers_range_equals.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_range_equals.hpp
diff --git a/src/catch2/matchers/catch_matchers_string.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_string.cpp
index 5500284..5500284 100644
--- a/src/catch2/matchers/catch_matchers_string.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_string.cpp
diff --git a/src/catch2/matchers/catch_matchers_string.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_string.hpp
index 61a385d..61a385d 100644
--- a/src/catch2/matchers/catch_matchers_string.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_string.hpp
diff --git a/src/catch2/matchers/catch_matchers_templated.cpp b/contrib/catch2/src/catch2/matchers/catch_matchers_templated.cpp
index 2fc529d..2fc529d 100644
--- a/src/catch2/matchers/catch_matchers_templated.cpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_templated.cpp
diff --git a/src/catch2/matchers/catch_matchers_templated.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_templated.hpp
index ba0661a..ba0661a 100644
--- a/src/catch2/matchers/catch_matchers_templated.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_templated.hpp
diff --git a/src/catch2/matchers/catch_matchers_vector.hpp b/contrib/catch2/src/catch2/matchers/catch_matchers_vector.hpp
index fffbfdf..fffbfdf 100644
--- a/src/catch2/matchers/catch_matchers_vector.hpp
+++ b/contrib/catch2/src/catch2/matchers/catch_matchers_vector.hpp
diff --git a/src/catch2/matchers/internal/catch_matchers_impl.cpp b/contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.cpp
index 41b462e..41b462e 100644
--- a/src/catch2/matchers/internal/catch_matchers_impl.cpp
+++ b/contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.cpp
diff --git a/src/catch2/matchers/internal/catch_matchers_impl.hpp b/contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.hpp
index 24a3f8b..24a3f8b 100644
--- a/src/catch2/matchers/internal/catch_matchers_impl.hpp
+++ b/contrib/catch2/src/catch2/matchers/internal/catch_matchers_impl.hpp
diff --git a/src/catch2/meson.build b/contrib/catch2/src/catch2/meson.build
index 2eb5399..2eb5399 100644
--- a/src/catch2/meson.build
+++ b/contrib/catch2/src/catch2/meson.build
diff --git a/src/catch2/reporters/catch_reporter_automake.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_automake.cpp
index 5e506a6..5e506a6 100644
--- a/src/catch2/reporters/catch_reporter_automake.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_automake.cpp
diff --git a/src/catch2/reporters/catch_reporter_automake.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_automake.hpp
index a639428..a639428 100644
--- a/src/catch2/reporters/catch_reporter_automake.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_automake.hpp
diff --git a/src/catch2/reporters/catch_reporter_common_base.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_common_base.cpp
index a1ca76a..a1ca76a 100644
--- a/src/catch2/reporters/catch_reporter_common_base.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_common_base.cpp
diff --git a/src/catch2/reporters/catch_reporter_common_base.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_common_base.hpp
index b4f0a9f..b4f0a9f 100644
--- a/src/catch2/reporters/catch_reporter_common_base.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_common_base.hpp
diff --git a/src/catch2/reporters/catch_reporter_compact.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_compact.cpp
index 0f85594..0f85594 100644
--- a/src/catch2/reporters/catch_reporter_compact.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_compact.cpp
diff --git a/src/catch2/reporters/catch_reporter_compact.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_compact.hpp
index d95bbff..d95bbff 100644
--- a/src/catch2/reporters/catch_reporter_compact.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_compact.hpp
diff --git a/src/catch2/reporters/catch_reporter_console.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_console.cpp
index 9529f39..9529f39 100644
--- a/src/catch2/reporters/catch_reporter_console.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_console.cpp
diff --git a/src/catch2/reporters/catch_reporter_console.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_console.hpp
index 2437726..2437726 100644
--- a/src/catch2/reporters/catch_reporter_console.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_console.hpp
diff --git a/src/catch2/reporters/catch_reporter_cumulative_base.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.cpp
index 0916963..0916963 100644
--- a/src/catch2/reporters/catch_reporter_cumulative_base.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.cpp
diff --git a/src/catch2/reporters/catch_reporter_cumulative_base.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.hpp
index 267b39f..267b39f 100644
--- a/src/catch2/reporters/catch_reporter_cumulative_base.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_cumulative_base.hpp
diff --git a/src/catch2/reporters/catch_reporter_event_listener.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.cpp
index e94063b..e94063b 100644
--- a/src/catch2/reporters/catch_reporter_event_listener.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.cpp
diff --git a/src/catch2/reporters/catch_reporter_event_listener.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.hpp
index 346263e..346263e 100644
--- a/src/catch2/reporters/catch_reporter_event_listener.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_event_listener.hpp
diff --git a/src/catch2/reporters/catch_reporter_helpers.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_helpers.cpp
index ffb32ff..ffb32ff 100644
--- a/src/catch2/reporters/catch_reporter_helpers.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_helpers.cpp
diff --git a/src/catch2/reporters/catch_reporter_helpers.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_helpers.hpp
index 316cb40..316cb40 100644
--- a/src/catch2/reporters/catch_reporter_helpers.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_helpers.hpp
diff --git a/src/catch2/reporters/catch_reporter_json.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_json.cpp
index 6a8e655..6a8e655 100644
--- a/src/catch2/reporters/catch_reporter_json.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_json.cpp
diff --git a/src/catch2/reporters/catch_reporter_json.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_json.hpp
index c938ca3..c938ca3 100644
--- a/src/catch2/reporters/catch_reporter_json.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_json.hpp
diff --git a/src/catch2/reporters/catch_reporter_junit.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_junit.cpp
index 27bdfe2..27bdfe2 100644
--- a/src/catch2/reporters/catch_reporter_junit.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_junit.cpp
diff --git a/src/catch2/reporters/catch_reporter_junit.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_junit.hpp
index 7cb53c2..7cb53c2 100644
--- a/src/catch2/reporters/catch_reporter_junit.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_junit.hpp
diff --git a/src/catch2/reporters/catch_reporter_multi.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_multi.cpp
index 531902b..531902b 100644
--- a/src/catch2/reporters/catch_reporter_multi.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_multi.cpp
diff --git a/src/catch2/reporters/catch_reporter_multi.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_multi.hpp
index 6611383..6611383 100644
--- a/src/catch2/reporters/catch_reporter_multi.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_multi.hpp
diff --git a/src/catch2/reporters/catch_reporter_registrars.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_registrars.cpp
index 2a3ac95..2a3ac95 100644
--- a/src/catch2/reporters/catch_reporter_registrars.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_registrars.cpp
diff --git a/src/catch2/reporters/catch_reporter_registrars.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_registrars.hpp
index a93963f..a93963f 100644
--- a/src/catch2/reporters/catch_reporter_registrars.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_registrars.hpp
diff --git a/src/catch2/reporters/catch_reporter_sonarqube.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.cpp
index 2c3eb1c..2c3eb1c 100644
--- a/src/catch2/reporters/catch_reporter_sonarqube.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.cpp
diff --git a/src/catch2/reporters/catch_reporter_sonarqube.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.hpp
index 509f411..509f411 100644
--- a/src/catch2/reporters/catch_reporter_sonarqube.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_sonarqube.hpp
diff --git a/src/catch2/reporters/catch_reporter_streaming_base.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.cpp
index f1cc425..f1cc425 100644
--- a/src/catch2/reporters/catch_reporter_streaming_base.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.cpp
diff --git a/src/catch2/reporters/catch_reporter_streaming_base.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.hpp
index 5448000..5448000 100644
--- a/src/catch2/reporters/catch_reporter_streaming_base.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_streaming_base.hpp
diff --git a/src/catch2/reporters/catch_reporter_tap.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_tap.cpp
index 67d406f..67d406f 100644
--- a/src/catch2/reporters/catch_reporter_tap.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_tap.cpp
diff --git a/src/catch2/reporters/catch_reporter_tap.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_tap.hpp
index e6889bb..e6889bb 100644
--- a/src/catch2/reporters/catch_reporter_tap.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_tap.hpp
diff --git a/src/catch2/reporters/catch_reporter_teamcity.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.cpp
index 38aa55a..38aa55a 100644
--- a/src/catch2/reporters/catch_reporter_teamcity.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.cpp
diff --git a/src/catch2/reporters/catch_reporter_teamcity.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.hpp
index 662e989..662e989 100644
--- a/src/catch2/reporters/catch_reporter_teamcity.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_teamcity.hpp
diff --git a/src/catch2/reporters/catch_reporter_xml.cpp b/contrib/catch2/src/catch2/reporters/catch_reporter_xml.cpp
index 35a3028..35a3028 100644
--- a/src/catch2/reporters/catch_reporter_xml.cpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_xml.cpp
diff --git a/src/catch2/reporters/catch_reporter_xml.hpp b/contrib/catch2/src/catch2/reporters/catch_reporter_xml.hpp
index bead7a8..bead7a8 100644
--- a/src/catch2/reporters/catch_reporter_xml.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporter_xml.hpp
diff --git a/src/catch2/reporters/catch_reporters_all.hpp b/contrib/catch2/src/catch2/reporters/catch_reporters_all.hpp
index 5c713fe..5c713fe 100644
--- a/src/catch2/reporters/catch_reporters_all.hpp
+++ b/contrib/catch2/src/catch2/reporters/catch_reporters_all.hpp
diff --git a/tests/BUILD.bazel b/contrib/catch2/tests/BUILD.bazel
index 5f0362f..5f0362f 100644
--- a/tests/BUILD.bazel
+++ b/contrib/catch2/tests/BUILD.bazel
diff --git a/tests/CMakeLists.txt b/contrib/catch2/tests/CMakeLists.txt
index 37a5977..37a5977 100644
--- a/tests/CMakeLists.txt
+++ b/contrib/catch2/tests/CMakeLists.txt
diff --git a/tests/ExtraTests/CMakeLists.txt b/contrib/catch2/tests/ExtraTests/CMakeLists.txt
index 3c38d67..3c38d67 100644
--- a/tests/ExtraTests/CMakeLists.txt
+++ b/contrib/catch2/tests/ExtraTests/CMakeLists.txt
diff --git a/tests/ExtraTests/ToDo.txt b/contrib/catch2/tests/ExtraTests/ToDo.txt
index a2ed36b..a2ed36b 100644
--- a/tests/ExtraTests/ToDo.txt
+++ b/contrib/catch2/tests/ExtraTests/ToDo.txt
diff --git a/tests/ExtraTests/X01-PrefixedMacros.cpp b/contrib/catch2/tests/ExtraTests/X01-PrefixedMacros.cpp
index d1c246e..d1c246e 100644
--- a/tests/ExtraTests/X01-PrefixedMacros.cpp
+++ b/contrib/catch2/tests/ExtraTests/X01-PrefixedMacros.cpp
diff --git a/tests/ExtraTests/X02-DisabledMacros.cpp b/contrib/catch2/tests/ExtraTests/X02-DisabledMacros.cpp
index 231adfb..231adfb 100644
--- a/tests/ExtraTests/X02-DisabledMacros.cpp
+++ b/contrib/catch2/tests/ExtraTests/X02-DisabledMacros.cpp
diff --git a/tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp b/contrib/catch2/tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp
index 5b6d4b1..5b6d4b1 100644
--- a/tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp
+++ b/contrib/catch2/tests/ExtraTests/X03-DisabledExceptions-DefaultHandler.cpp
diff --git a/tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp b/contrib/catch2/tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp
index e1f1e70..e1f1e70 100644
--- a/tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp
+++ b/contrib/catch2/tests/ExtraTests/X04-DisabledExceptions-CustomHandler.cpp
diff --git a/tests/ExtraTests/X05-DeferredStaticChecks.cpp b/contrib/catch2/tests/ExtraTests/X05-DeferredStaticChecks.cpp
index 8005dbc..8005dbc 100644
--- a/tests/ExtraTests/X05-DeferredStaticChecks.cpp
+++ b/contrib/catch2/tests/ExtraTests/X05-DeferredStaticChecks.cpp
diff --git a/tests/ExtraTests/X10-FallbackStringifier.cpp b/contrib/catch2/tests/ExtraTests/X10-FallbackStringifier.cpp
index 8525a81..8525a81 100644
--- a/tests/ExtraTests/X10-FallbackStringifier.cpp
+++ b/contrib/catch2/tests/ExtraTests/X10-FallbackStringifier.cpp
diff --git a/tests/ExtraTests/X11-DisableStringification.cpp b/contrib/catch2/tests/ExtraTests/X11-DisableStringification.cpp
index f899627..f899627 100644
--- a/tests/ExtraTests/X11-DisableStringification.cpp
+++ b/contrib/catch2/tests/ExtraTests/X11-DisableStringification.cpp
diff --git a/tests/ExtraTests/X12-CustomDebugBreakMacro.cpp b/contrib/catch2/tests/ExtraTests/X12-CustomDebugBreakMacro.cpp
index e6d3e38..e6d3e38 100644
--- a/tests/ExtraTests/X12-CustomDebugBreakMacro.cpp
+++ b/contrib/catch2/tests/ExtraTests/X12-CustomDebugBreakMacro.cpp
diff --git a/tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp b/contrib/catch2/tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
index 6f44bf6..6f44bf6 100644
--- a/tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
+++ b/contrib/catch2/tests/ExtraTests/X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
diff --git a/tests/ExtraTests/X21-PartialTestCaseEvents.cpp b/contrib/catch2/tests/ExtraTests/X21-PartialTestCaseEvents.cpp
index aa5204c..aa5204c 100644
--- a/tests/ExtraTests/X21-PartialTestCaseEvents.cpp
+++ b/contrib/catch2/tests/ExtraTests/X21-PartialTestCaseEvents.cpp
diff --git a/tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp b/contrib/catch2/tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp
index 33399a6..33399a6 100644
--- a/tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp
+++ b/contrib/catch2/tests/ExtraTests/X22-BenchmarksInCumulativeReporter.cpp
diff --git a/tests/ExtraTests/X23-CasingInReporterNames.cpp b/contrib/catch2/tests/ExtraTests/X23-CasingInReporterNames.cpp
index 3052373..3052373 100644
--- a/tests/ExtraTests/X23-CasingInReporterNames.cpp
+++ b/contrib/catch2/tests/ExtraTests/X23-CasingInReporterNames.cpp
diff --git a/tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp b/contrib/catch2/tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp
index ffb706e..ffb706e 100644
--- a/tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp
+++ b/contrib/catch2/tests/ExtraTests/X24-ListenerStdoutCaptureInMultireporter.cpp
diff --git a/tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp b/contrib/catch2/tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp
index de75ef4..de75ef4 100644
--- a/tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp
+++ b/contrib/catch2/tests/ExtraTests/X25-ListenerCanAskForCapturedStdout.cpp
diff --git a/tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp b/contrib/catch2/tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp
index 90a5885..90a5885 100644
--- a/tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp
+++ b/contrib/catch2/tests/ExtraTests/X26-ReporterPreferencesForPassingAssertionsIsRespected.cpp
diff --git a/tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp b/contrib/catch2/tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp
index 61aaa02..61aaa02 100644
--- a/tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp
+++ b/contrib/catch2/tests/ExtraTests/X27-CapturedStdoutInTestCaseEvents.cpp
diff --git a/tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp b/contrib/catch2/tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp
index 4098c8b..4098c8b 100644
--- a/tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp
+++ b/contrib/catch2/tests/ExtraTests/X28-ListenersGetEventsBeforeReporters.cpp
diff --git a/tests/ExtraTests/X29-CustomArgumentsForReporters.cpp b/contrib/catch2/tests/ExtraTests/X29-CustomArgumentsForReporters.cpp
index 13d9fc1..13d9fc1 100644
--- a/tests/ExtraTests/X29-CustomArgumentsForReporters.cpp
+++ b/contrib/catch2/tests/ExtraTests/X29-CustomArgumentsForReporters.cpp
diff --git a/tests/ExtraTests/X30-BazelReporter.cpp b/contrib/catch2/tests/ExtraTests/X30-BazelReporter.cpp
index 2a25941..2a25941 100644
--- a/tests/ExtraTests/X30-BazelReporter.cpp
+++ b/contrib/catch2/tests/ExtraTests/X30-BazelReporter.cpp
diff --git a/tests/ExtraTests/X31-DuplicatedTestCases.cpp b/contrib/catch2/tests/ExtraTests/X31-DuplicatedTestCases.cpp
index 0d09175..0d09175 100644
--- a/tests/ExtraTests/X31-DuplicatedTestCases.cpp
+++ b/contrib/catch2/tests/ExtraTests/X31-DuplicatedTestCases.cpp
diff --git a/tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp b/contrib/catch2/tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp
index b0aa417..b0aa417 100644
--- a/tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp
+++ b/contrib/catch2/tests/ExtraTests/X32-DuplicatedTestCasesDifferentTags.cpp
diff --git a/tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp b/contrib/catch2/tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp
index d3c3449..d3c3449 100644
--- a/tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp
+++ b/contrib/catch2/tests/ExtraTests/X33-DuplicatedTestCaseMethods.cpp
diff --git a/tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp b/contrib/catch2/tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp
index 397cf15..397cf15 100644
--- a/tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp
+++ b/contrib/catch2/tests/ExtraTests/X34-DuplicatedTestCaseMethodsDifferentFixtures.cpp
diff --git a/tests/ExtraTests/X35-DuplicatedReporterNames.cpp b/contrib/catch2/tests/ExtraTests/X35-DuplicatedReporterNames.cpp
index a978448..a978448 100644
--- a/tests/ExtraTests/X35-DuplicatedReporterNames.cpp
+++ b/contrib/catch2/tests/ExtraTests/X35-DuplicatedReporterNames.cpp
diff --git a/tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp b/contrib/catch2/tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp
index 34f4cd8..34f4cd8 100644
--- a/tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp
+++ b/contrib/catch2/tests/ExtraTests/X36-ReportingCrashWithJunitReporter.cpp
diff --git a/tests/ExtraTests/X90-WindowsHeaderInclusion.cpp b/contrib/catch2/tests/ExtraTests/X90-WindowsHeaderInclusion.cpp
index dff542a..dff542a 100644
--- a/tests/ExtraTests/X90-WindowsHeaderInclusion.cpp
+++ b/contrib/catch2/tests/ExtraTests/X90-WindowsHeaderInclusion.cpp
diff --git a/tests/ExtraTests/X91-AmalgamatedCatch.cpp b/contrib/catch2/tests/ExtraTests/X91-AmalgamatedCatch.cpp
index 78d45a2..78d45a2 100644
--- a/tests/ExtraTests/X91-AmalgamatedCatch.cpp
+++ b/contrib/catch2/tests/ExtraTests/X91-AmalgamatedCatch.cpp
diff --git a/tests/ExtraTests/X92-NoTests.cpp b/contrib/catch2/tests/ExtraTests/X92-NoTests.cpp
index 9591e54..9591e54 100644
--- a/tests/ExtraTests/X92-NoTests.cpp
+++ b/contrib/catch2/tests/ExtraTests/X92-NoTests.cpp
diff --git a/tests/ExtraTests/X93-AllSkipped.cpp b/contrib/catch2/tests/ExtraTests/X93-AllSkipped.cpp
index 8e7d0af..8e7d0af 100644
--- a/tests/ExtraTests/X93-AllSkipped.cpp
+++ b/contrib/catch2/tests/ExtraTests/X93-AllSkipped.cpp
diff --git a/tests/SelfTest/Baselines/automake.std.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/automake.std.approved.txt
index 057be06..057be06 100644
--- a/tests/SelfTest/Baselines/automake.std.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/automake.std.approved.txt
diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/automake.sw.approved.txt
index 7be343a..7be343a 100644
--- a/tests/SelfTest/Baselines/automake.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/automake.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
index 0cd24e4..0cd24e4 100644
--- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/compact.sw.approved.txt
index b7f48cb..b7f48cb 100644
--- a/tests/SelfTest/Baselines/compact.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/compact.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
index 9e3e537..9e3e537 100644
--- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/console.std.approved.txt
index 495264b..495264b 100644
--- a/tests/SelfTest/Baselines/console.std.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/console.std.approved.txt
diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/console.sw.approved.txt
index a53f40d..a53f40d 100644
--- a/tests/SelfTest/Baselines/console.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/console.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/console.sw.multi.approved.txt
index 418ccf7..418ccf7 100644
--- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/console.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/console.swa4.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/console.swa4.approved.txt
index 41b7612..41b7612 100644
--- a/tests/SelfTest/Baselines/console.swa4.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/console.swa4.approved.txt
diff --git a/tests/SelfTest/Baselines/default.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/default.sw.multi.approved.txt
index bb17484..bb17484 100644
--- a/tests/SelfTest/Baselines/default.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/default.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/junit.sw.approved.txt
index c3571f3..c3571f3 100644
--- a/tests/SelfTest/Baselines/junit.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/junit.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
index 55f5520..55f5520 100644
--- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
index af06793..af06793 100644
--- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
index 2d8d510..2d8d510 100644
--- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/tap.sw.approved.txt
index 801946d..801946d 100644
--- a/tests/SelfTest/Baselines/tap.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/tap.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
index 7f281f6..7f281f6 100644
--- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.approved.txt
index db48552..db48552 100644
--- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
index dde8a68..dde8a68 100644
--- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/xml.sw.approved.txt
index aa6bd6a..aa6bd6a 100644
--- a/tests/SelfTest/Baselines/xml.sw.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/xml.sw.approved.txt
diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/contrib/catch2/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
index 7f3b498..7f3b498 100644
--- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
+++ b/contrib/catch2/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
diff --git a/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
index fa17cf8..fa17cf8 100644
--- a/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp
index ab09607..ab09607 100644
--- a/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
index 53023b5..53023b5 100644
--- a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp
index 404bad2..404bad2 100644
--- a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp
index 4df8ecf..4df8ecf 100644
--- a/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp
index 615fda1..615fda1 100644
--- a/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Details.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
index 5566bb5..5566bb5 100644
--- a/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
index d218170..d218170 100644
--- a/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
index 14c9011..14c9011 100644
--- a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp
index 8955f40..8955f40 100644
--- a/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
index 69251d9..69251d9 100644
--- a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Json.tests.cpp
index 8204e3c..8204e3c 100644
--- a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Json.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
index 7791355..7791355 100644
--- a/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp
index c13ec57..c13ec57 100644
--- a/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
index 8932321..8932321 100644
--- a/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
index 86ba117..86ba117 100644
--- a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp
index 8e6009d..8e6009d 100644
--- a/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp
index 738cb52..738cb52 100644
--- a/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/String.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/String.tests.cpp
index 43c58b4..43c58b4 100644
--- a/tests/SelfTest/IntrospectiveTests/String.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/String.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
index f30573c..f30573c 100644
--- a/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
index 4372375..4372375 100644
--- a/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
index 03cb3f0..03cb3f0 100644
--- a/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp
index 11a7a7a..11a7a7a 100644
--- a/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp
index ae27b40..ae27b40 100644
--- a/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
index de03ed0..de03ed0 100644
--- a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
index e190460..e190460 100644
--- a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
index 459e0d4..459e0d4 100644
--- a/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp
index 420bf1b..420bf1b 100644
--- a/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp
diff --git a/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp
index b5982b8..b5982b8 100644
--- a/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp
diff --git a/tests/SelfTest/Misc/invalid-test-names.input b/contrib/catch2/tests/SelfTest/Misc/invalid-test-names.input
index e2bc88e..e2bc88e 100644
--- a/tests/SelfTest/Misc/invalid-test-names.input
+++ b/contrib/catch2/tests/SelfTest/Misc/invalid-test-names.input
diff --git a/tests/SelfTest/Misc/plain-old-tests.input b/contrib/catch2/tests/SelfTest/Misc/plain-old-tests.input
index ae6d9f1..ae6d9f1 100644
--- a/tests/SelfTest/Misc/plain-old-tests.input
+++ b/contrib/catch2/tests/SelfTest/Misc/plain-old-tests.input
diff --git a/tests/SelfTest/Misc/special-characters-in-file.input b/contrib/catch2/tests/SelfTest/Misc/special-characters-in-file.input
index b68a6bf..b68a6bf 100644
--- a/tests/SelfTest/Misc/special-characters-in-file.input
+++ b/contrib/catch2/tests/SelfTest/Misc/special-characters-in-file.input
diff --git a/tests/SelfTest/TestRegistrations.cpp b/contrib/catch2/tests/SelfTest/TestRegistrations.cpp
index d7a6966..d7a6966 100644
--- a/tests/SelfTest/TestRegistrations.cpp
+++ b/contrib/catch2/tests/SelfTest/TestRegistrations.cpp
diff --git a/tests/SelfTest/TimingTests/Sleep.tests.cpp b/contrib/catch2/tests/SelfTest/TimingTests/Sleep.tests.cpp
index 4e9e385..4e9e385 100644
--- a/tests/SelfTest/TimingTests/Sleep.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/TimingTests/Sleep.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Approx.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Approx.tests.cpp
index c3d41cb..c3d41cb 100644
--- a/tests/SelfTest/UsageTests/Approx.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Approx.tests.cpp
diff --git a/tests/SelfTest/UsageTests/BDD.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/BDD.tests.cpp
index 5ac8605..5ac8605 100644
--- a/tests/SelfTest/UsageTests/BDD.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/BDD.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Benchmark.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Benchmark.tests.cpp
index c489eaa..c489eaa 100644
--- a/tests/SelfTest/UsageTests/Benchmark.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Benchmark.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Class.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Class.tests.cpp
index 75510f1..75510f1 100644
--- a/tests/SelfTest/UsageTests/Class.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Class.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Compilation.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Compilation.tests.cpp
index 7a6a1f2..7a6a1f2 100644
--- a/tests/SelfTest/UsageTests/Compilation.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Compilation.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Condition.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Condition.tests.cpp
index 211dd3b..211dd3b 100644
--- a/tests/SelfTest/UsageTests/Condition.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Condition.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Decomposition.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Decomposition.tests.cpp
index e92f740..e92f740 100644
--- a/tests/SelfTest/UsageTests/Decomposition.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Decomposition.tests.cpp
diff --git a/tests/SelfTest/UsageTests/EnumToString.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/EnumToString.tests.cpp
index 268a7ca..268a7ca 100644
--- a/tests/SelfTest/UsageTests/EnumToString.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/EnumToString.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Exception.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Exception.tests.cpp
index 7c6b0c8..7c6b0c8 100644
--- a/tests/SelfTest/UsageTests/Exception.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Exception.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Generators.tests.cpp
index f04cf4f..f04cf4f 100644
--- a/tests/SelfTest/UsageTests/Generators.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Generators.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Matchers.tests.cpp
index 7c4501c..7c4501c 100644
--- a/tests/SelfTest/UsageTests/Matchers.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Matchers.tests.cpp
diff --git a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp
index 4f906b9..4f906b9 100644
--- a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Message.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Message.tests.cpp
index 7626e00..7626e00 100644
--- a/tests/SelfTest/UsageTests/Message.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Message.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Misc.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Misc.tests.cpp
index 3697f06..3697f06 100644
--- a/tests/SelfTest/UsageTests/Misc.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Misc.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Skip.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Skip.tests.cpp
index 661795e..661795e 100644
--- a/tests/SelfTest/UsageTests/Skip.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Skip.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringByte.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringByte.tests.cpp
index 624abbf..624abbf 100644
--- a/tests/SelfTest/UsageTests/ToStringByte.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringByte.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp
index 744b899..744b899 100644
--- a/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp
index 78c0c80..78c0c80 100644
--- a/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp
index 3671771..3671771 100644
--- a/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringPair.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringPair.tests.cpp
index f5cb239..f5cb239 100644
--- a/tests/SelfTest/UsageTests/ToStringPair.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringPair.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp
index 9d1d2c4..9d1d2c4 100644
--- a/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp
index 197ba55..197ba55 100644
--- a/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringVector.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringVector.tests.cpp
index c042744..c042744 100644
--- a/tests/SelfTest/UsageTests/ToStringVector.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringVector.tests.cpp
diff --git a/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp
index ec7a49e..ec7a49e 100644
--- a/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp
diff --git a/tests/SelfTest/UsageTests/Tricky.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/Tricky.tests.cpp
index 041d786..041d786 100644
--- a/tests/SelfTest/UsageTests/Tricky.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/Tricky.tests.cpp
diff --git a/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp b/contrib/catch2/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp
index 92048a0..92048a0 100644
--- a/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp
+++ b/contrib/catch2/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp
diff --git a/tests/SelfTest/helpers/parse_test_spec.cpp b/contrib/catch2/tests/SelfTest/helpers/parse_test_spec.cpp
index aa64404..aa64404 100644
--- a/tests/SelfTest/helpers/parse_test_spec.cpp
+++ b/contrib/catch2/tests/SelfTest/helpers/parse_test_spec.cpp
diff --git a/tests/SelfTest/helpers/parse_test_spec.hpp b/contrib/catch2/tests/SelfTest/helpers/parse_test_spec.hpp
index 39ee071..39ee071 100644
--- a/tests/SelfTest/helpers/parse_test_spec.hpp
+++ b/contrib/catch2/tests/SelfTest/helpers/parse_test_spec.hpp
diff --git a/tests/SelfTest/helpers/range_test_helpers.hpp b/contrib/catch2/tests/SelfTest/helpers/range_test_helpers.hpp
index 22c2c3c..22c2c3c 100644
--- a/tests/SelfTest/helpers/range_test_helpers.hpp
+++ b/contrib/catch2/tests/SelfTest/helpers/range_test_helpers.hpp
diff --git a/tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp b/contrib/catch2/tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp
index a8e517c..a8e517c 100644
--- a/tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp
+++ b/contrib/catch2/tests/SelfTest/helpers/type_with_lit_0_comparisons.hpp
diff --git a/tests/TestScripts/ConfigureTestsCommon.py b/contrib/catch2/tests/TestScripts/ConfigureTestsCommon.py
index 13b1d92..13b1d92 100644
--- a/tests/TestScripts/ConfigureTestsCommon.py
+++ b/contrib/catch2/tests/TestScripts/ConfigureTestsCommon.py
diff --git a/tests/TestScripts/DiscoverTests/CMakeLists.txt b/contrib/catch2/tests/TestScripts/DiscoverTests/CMakeLists.txt
index f0b49f4..f0b49f4 100644
--- a/tests/TestScripts/DiscoverTests/CMakeLists.txt
+++ b/contrib/catch2/tests/TestScripts/DiscoverTests/CMakeLists.txt
diff --git a/tests/TestScripts/DiscoverTests/VerifyRegistration.py b/contrib/catch2/tests/TestScripts/DiscoverTests/VerifyRegistration.py
index 33150ca..33150ca 100644
--- a/tests/TestScripts/DiscoverTests/VerifyRegistration.py
+++ b/contrib/catch2/tests/TestScripts/DiscoverTests/VerifyRegistration.py
diff --git a/tests/TestScripts/DiscoverTests/register-tests.cpp b/contrib/catch2/tests/TestScripts/DiscoverTests/register-tests.cpp
index be533ab..be533ab 100644
--- a/tests/TestScripts/DiscoverTests/register-tests.cpp
+++ b/contrib/catch2/tests/TestScripts/DiscoverTests/register-tests.cpp
diff --git a/tests/TestScripts/testBazelReporter.py b/contrib/catch2/tests/TestScripts/testBazelReporter.py
index 573eafd..573eafd 100644
--- a/tests/TestScripts/testBazelReporter.py
+++ b/contrib/catch2/tests/TestScripts/testBazelReporter.py
diff --git a/tests/TestScripts/testBazelSharding.py b/contrib/catch2/tests/TestScripts/testBazelSharding.py
index d4a9aee..d4a9aee 100755
--- a/tests/TestScripts/testBazelSharding.py
+++ b/contrib/catch2/tests/TestScripts/testBazelSharding.py
diff --git a/tests/TestScripts/testConfigureDefaultReporter.py b/contrib/catch2/tests/TestScripts/testConfigureDefaultReporter.py
index 119e1ca..119e1ca 100644
--- a/tests/TestScripts/testConfigureDefaultReporter.py
+++ b/contrib/catch2/tests/TestScripts/testConfigureDefaultReporter.py
diff --git a/tests/TestScripts/testConfigureDisable.py b/contrib/catch2/tests/TestScripts/testConfigureDisable.py
index b024770..b024770 100644
--- a/tests/TestScripts/testConfigureDisable.py
+++ b/contrib/catch2/tests/TestScripts/testConfigureDisable.py
diff --git a/tests/TestScripts/testConfigureDisableStringification.py b/contrib/catch2/tests/TestScripts/testConfigureDisableStringification.py
index 33f7853..33f7853 100644
--- a/tests/TestScripts/testConfigureDisableStringification.py
+++ b/contrib/catch2/tests/TestScripts/testConfigureDisableStringification.py
diff --git a/tests/TestScripts/testConfigureExperimentalRedirect.py b/contrib/catch2/tests/TestScripts/testConfigureExperimentalRedirect.py
index 09ff51e..09ff51e 100644
--- a/tests/TestScripts/testConfigureExperimentalRedirect.py
+++ b/contrib/catch2/tests/TestScripts/testConfigureExperimentalRedirect.py
diff --git a/tests/TestScripts/testPartialTestCaseEvent.py b/contrib/catch2/tests/TestScripts/testPartialTestCaseEvent.py
index 55c6e29..55c6e29 100755
--- a/tests/TestScripts/testPartialTestCaseEvent.py
+++ b/contrib/catch2/tests/TestScripts/testPartialTestCaseEvent.py
diff --git a/tests/TestScripts/testRandomOrder.py b/contrib/catch2/tests/TestScripts/testRandomOrder.py
index 2368423..2368423 100755
--- a/tests/TestScripts/testRandomOrder.py
+++ b/contrib/catch2/tests/TestScripts/testRandomOrder.py
diff --git a/tests/TestScripts/testSharding.py b/contrib/catch2/tests/TestScripts/testSharding.py
index fa6f94d..fa6f94d 100755
--- a/tests/TestScripts/testSharding.py
+++ b/contrib/catch2/tests/TestScripts/testSharding.py
diff --git a/tests/meson.build b/contrib/catch2/tests/meson.build
index 58302b7..58302b7 100644
--- a/tests/meson.build
+++ b/contrib/catch2/tests/meson.build
diff --git a/third_party/clara.hpp b/contrib/catch2/third_party/clara.hpp
index eb4c727..eb4c727 100644
--- a/third_party/clara.hpp
+++ b/contrib/catch2/third_party/clara.hpp
diff --git a/tools/misc/CMakeLists.txt b/contrib/catch2/tools/misc/CMakeLists.txt
index 59811df..59811df 100644
--- a/tools/misc/CMakeLists.txt
+++ b/contrib/catch2/tools/misc/CMakeLists.txt
diff --git a/tools/misc/appveyorBuildConfigurationScript.bat b/contrib/catch2/tools/misc/appveyorBuildConfigurationScript.bat
index 727f829..727f829 100644
--- a/tools/misc/appveyorBuildConfigurationScript.bat
+++ b/contrib/catch2/tools/misc/appveyorBuildConfigurationScript.bat
diff --git a/tools/misc/appveyorMergeCoverageScript.py b/contrib/catch2/tools/misc/appveyorMergeCoverageScript.py
index 5b71f6e..5b71f6e 100644
--- a/tools/misc/appveyorMergeCoverageScript.py
+++ b/contrib/catch2/tools/misc/appveyorMergeCoverageScript.py
diff --git a/tools/misc/appveyorTestRunScript.bat b/contrib/catch2/tools/misc/appveyorTestRunScript.bat
index 661bae2..661bae2 100644
--- a/tools/misc/appveyorTestRunScript.bat
+++ b/contrib/catch2/tools/misc/appveyorTestRunScript.bat
diff --git a/tools/misc/coverage-helper.cpp b/contrib/catch2/tools/misc/coverage-helper.cpp
index 9e7a8ca..9e7a8ca 100644
--- a/tools/misc/coverage-helper.cpp
+++ b/contrib/catch2/tools/misc/coverage-helper.cpp
diff --git a/tools/misc/installOpenCppCoverage.ps1 b/contrib/catch2/tools/misc/installOpenCppCoverage.ps1
index 215fe20..215fe20 100644
--- a/tools/misc/installOpenCppCoverage.ps1
+++ b/contrib/catch2/tools/misc/installOpenCppCoverage.ps1
diff --git a/tools/scripts/approvalTests.py b/contrib/catch2/tools/scripts/approvalTests.py
index 4146b64..4146b64 100755
--- a/tools/scripts/approvalTests.py
+++ b/contrib/catch2/tools/scripts/approvalTests.py
diff --git a/tools/scripts/approve.py b/contrib/catch2/tools/scripts/approve.py
index 6d73be5..6d73be5 100755
--- a/tools/scripts/approve.py
+++ b/contrib/catch2/tools/scripts/approve.py
diff --git a/tools/scripts/buildAndTest.cmd b/contrib/catch2/tools/scripts/buildAndTest.cmd
index fa35912..fa35912 100644
--- a/tools/scripts/buildAndTest.cmd
+++ b/contrib/catch2/tools/scripts/buildAndTest.cmd
diff --git a/tools/scripts/buildAndTest.sh b/contrib/catch2/tools/scripts/buildAndTest.sh
index 0383c97..0383c97 100755
--- a/tools/scripts/buildAndTest.sh
+++ b/contrib/catch2/tools/scripts/buildAndTest.sh
diff --git a/tools/scripts/checkConvenienceHeaders.py b/contrib/catch2/tools/scripts/checkConvenienceHeaders.py
index 41b52ce..41b52ce 100755
--- a/tools/scripts/checkConvenienceHeaders.py
+++ b/contrib/catch2/tools/scripts/checkConvenienceHeaders.py
diff --git a/tools/scripts/checkDuplicateFilenames.py b/contrib/catch2/tools/scripts/checkDuplicateFilenames.py
index b46a2b4..b46a2b4 100755
--- a/tools/scripts/checkDuplicateFilenames.py
+++ b/contrib/catch2/tools/scripts/checkDuplicateFilenames.py
diff --git a/tools/scripts/checkLicense.py b/contrib/catch2/tools/scripts/checkLicense.py
index 7078d3e..7078d3e 100755
--- a/tools/scripts/checkLicense.py
+++ b/contrib/catch2/tools/scripts/checkLicense.py
diff --git a/tools/scripts/developBuild.py b/contrib/catch2/tools/scripts/developBuild.py
index 8837770..8837770 100755
--- a/tools/scripts/developBuild.py
+++ b/contrib/catch2/tools/scripts/developBuild.py
diff --git a/tools/scripts/extractFeaturesFromReleaseNotes.py b/contrib/catch2/tools/scripts/extractFeaturesFromReleaseNotes.py
index d8be043..d8be043 100644
--- a/tools/scripts/extractFeaturesFromReleaseNotes.py
+++ b/contrib/catch2/tools/scripts/extractFeaturesFromReleaseNotes.py
diff --git a/tools/scripts/fixWhitespace.py b/contrib/catch2/tools/scripts/fixWhitespace.py
index 5840e79..5840e79 100755
--- a/tools/scripts/fixWhitespace.py
+++ b/contrib/catch2/tools/scripts/fixWhitespace.py
diff --git a/tools/scripts/generateAmalgamatedFiles.py b/contrib/catch2/tools/scripts/generateAmalgamatedFiles.py
index e3e86aa..e3e86aa 100755
--- a/tools/scripts/generateAmalgamatedFiles.py
+++ b/contrib/catch2/tools/scripts/generateAmalgamatedFiles.py
diff --git a/tools/scripts/majorRelease.py b/contrib/catch2/tools/scripts/majorRelease.py
index eb712b4..eb712b4 100755
--- a/tools/scripts/majorRelease.py
+++ b/contrib/catch2/tools/scripts/majorRelease.py
diff --git a/tools/scripts/minorRelease.py b/contrib/catch2/tools/scripts/minorRelease.py
index 0992c8f..0992c8f 100755
--- a/tools/scripts/minorRelease.py
+++ b/contrib/catch2/tools/scripts/minorRelease.py
diff --git a/tools/scripts/patchRelease.py b/contrib/catch2/tools/scripts/patchRelease.py
index 48256c1..48256c1 100755
--- a/tools/scripts/patchRelease.py
+++ b/contrib/catch2/tools/scripts/patchRelease.py
diff --git a/tools/scripts/releaseCommon.py b/contrib/catch2/tools/scripts/releaseCommon.py
index 81efa76..81efa76 100644
--- a/tools/scripts/releaseCommon.py
+++ b/contrib/catch2/tools/scripts/releaseCommon.py
diff --git a/tools/scripts/scriptCommon.py b/contrib/catch2/tools/scripts/scriptCommon.py
index 5894185..5894185 100644
--- a/tools/scripts/scriptCommon.py
+++ b/contrib/catch2/tools/scripts/scriptCommon.py
diff --git a/tools/scripts/updateDocumentSnippets.py b/contrib/catch2/tools/scripts/updateDocumentSnippets.py
index a070eea..a070eea 100755
--- a/tools/scripts/updateDocumentSnippets.py
+++ b/contrib/catch2/tools/scripts/updateDocumentSnippets.py
diff --git a/tools/scripts/updateDocumentToC.py b/contrib/catch2/tools/scripts/updateDocumentToC.py
index 1840cec..1840cec 100755
--- a/tools/scripts/updateDocumentToC.py
+++ b/contrib/catch2/tools/scripts/updateDocumentToC.py
diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt
new file mode 100644
index 0000000..b05ed3a
--- /dev/null
+++ b/nihil.cli/CMakeLists.txt
@@ -0,0 +1,40 @@
+# This source code is released into the public domain.
+
+add_library(nihil.cli STATIC)
+target_link_libraries(nihil.cli PRIVATE nihil.util)
+target_sources(nihil.cli
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.cli.ccm
+
+ command.ccm
+ command_tree.ccm
+ command_node.ccm
+ dispatch_command.ccm
+ registry.ccm
+ usage_error.ccm
+
+ PRIVATE
+ command.cc
+ command_tree.cc
+ command_node.cc
+ dispatch_command.cc
+ registry.cc
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.cli.test
+ test.cc
+ )
+ target_link_libraries(nihil.cli.test PRIVATE
+ nihil.cli
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.cli.test)
+endif()
diff --git a/nihil.cli/command.cc b/nihil.cli/command.cc
new file mode 100644
index 0000000..725b4eb
--- /dev/null
+++ b/nihil.cli/command.cc
@@ -0,0 +1,52 @@
+/*
+ * 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.
+#include <sysexits.h>
+
+module nihil.cli;
+
+import nihil.error;
+import :registry;
+
+namespace nihil {
+
+//NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+command::command(std::string_view path, std::string_view usage,
+ command_function_t handler)
+ : command_node(path)
+ , m_usage(usage)
+ , m_handler(std::move(handler))
+{
+ register_command(this);
+}
+
+auto command::usage(this command const &self) noexcept -> std::string_view
+{
+ return self.m_usage;
+}
+
+auto command::invoke(int argc, char **argv) const
+ -> std::expected<int, error>
+{
+ try {
+ return std::invoke(m_handler, argc, argv);
+ } catch (usage_error const &err) {
+ std::print(std::cerr, "{}\n", err.what());
+ std::print(std::cerr, "usage: {} {}", path(), usage());
+ return EX_USAGE;
+ }
+}
+
+} // namespace nihil
diff --git a/nihil.cli/command.ccm b/nihil.cli/command.ccm
new file mode 100644
index 0000000..74ef030
--- /dev/null
+++ b/nihil.cli/command.ccm
@@ -0,0 +1,48 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <functional>
+#include <string>
+
+export module nihil.cli:command;
+
+import nihil.error;
+import :command_node;
+
+namespace nihil {
+
+export struct command;
+
+/*
+ * A command that can be invoked. Instantiating a command adds this command
+ * to the global command table. If an error occurs, the program will abort.
+ */
+
+using command_handler_t = int (int, char **);
+using command_function_t = std::function<command_handler_t>;
+
+export struct command final : command_node {
+ command(std::string_view path, std::string_view usage,
+ command_function_t);
+
+ command(std::string_view path, std::string_view usage, auto &&fn)
+ : command(path, usage, command_function_t(fn))
+ {}
+
+ [[nodiscard]] auto usage(this command const &) noexcept
+ -> std::string_view;
+
+ [[nodiscard]] auto invoke(int argc, char **argv) const
+ -> std::expected<int, error> override;
+
+private:
+ std::string_view m_path;
+ std::string_view m_usage;
+ command_function_t m_handler;
+};
+
+} // namespace nihil
diff --git a/nihil.cli/command_node.cc b/nihil.cli/command_node.cc
new file mode 100644
index 0000000..dd18716
--- /dev/null
+++ b/nihil.cli/command_node.cc
@@ -0,0 +1,49 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <iostream>
+#include <print>
+#include <string>
+
+#include <unistd.h>
+
+module nihil.cli;
+
+import nihil.core;
+import nihil.error;
+
+namespace nihil {
+
+//NOLINTNEXTLINE(bugprone-exception-escape)
+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());
+ _exit(1);
+ /*NOTREACHED*/
+}
+
+command_node::~command_node()
+{
+}
+
+auto command_node::path(this command_node const &self) noexcept
+ -> std::string_view
+{
+ return self.m_path;
+}
+
+auto command_node::invoke(int, char **) const
+ -> std::expected<int, error>
+{
+ // If invoke() wasn't overridden, then this is an empty node,
+ // so the command was incomplete.
+ return std::unexpected(error(errc::incomplete_command));
+}
+
+} // namespace nihil
diff --git a/nihil.cli/command_node.ccm b/nihil.cli/command_node.ccm
new file mode 100644
index 0000000..546eb46
--- /dev/null
+++ b/nihil.cli/command_node.ccm
@@ -0,0 +1,35 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * command_node represents a possibly-invocable command.
+ */
+
+#include <expected>
+#include <string>
+
+export module nihil.cli:command_node;
+
+import nihil.error;
+
+namespace nihil {
+
+export struct command_node {
+ command_node(std::string_view path) noexcept;
+
+ virtual ~command_node();
+
+ [[nodiscard]] auto path(this command_node const &) noexcept
+ -> std::string_view;
+
+ [[nodiscard]] virtual auto invoke(int argc, char **argv) const
+ -> std::expected<int, error>;
+
+private:
+ std::string m_path;
+};
+
+} // namespace nihil
diff --git a/nihil.cli/command_tree.cc b/nihil.cli/command_tree.cc
new file mode 100644
index 0000000..2d14669
--- /dev/null
+++ b/nihil.cli/command_tree.cc
@@ -0,0 +1,179 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <iostream>
+#include <memory>
+#include <print>
+#include <ranges>
+#include <string>
+#include <vector>
+
+module nihil.cli;
+
+namespace nihil {
+
+command_tree_node::command_tree_node()
+ : m_this_word("")
+ , m_command(std::make_shared<command_node>(command_node("")))
+{
+}
+
+command_tree_node::command_tree_node(command_tree_node *parent,
+ std::string_view this_word)
+ : m_parent(parent)
+ , m_this_word(this_word)
+{
+}
+
+command_tree_node::command_tree_node(command_tree_node *parent,
+ std::string_view this_word,
+ std::shared_ptr<command_node> command)
+ : m_parent(parent)
+ , m_this_word(this_word)
+ , m_command(std::move(command))
+{
+}
+
+auto command_tree_node::get_child(this command_tree_node const &self,
+ std::string_view child)
+ -> command_tree_node const *
+{
+ if (auto it = self.m_children.find(std::string(child));
+ it != self.m_children.end())
+ return &it->second;
+
+ return nullptr;
+}
+
+auto command_tree_node::get_child(this command_tree_node &self,
+ std::string_view child)
+ -> command_tree_node *
+{
+ if (auto it = self.m_children.find(std::string(child));
+ it != self.m_children.end())
+ return &it->second;
+
+ return nullptr;
+}
+
+auto command_tree_node::get_or_create_child(this command_tree_node &self,
+ std::string_view child)
+ -> command_tree_node *
+{
+ // Return the existing child, if there is one.
+ if (auto ptr = self.get_child(child); ptr != nullptr)
+ return ptr;
+
+ // Insert a new child.
+ auto [it, ok] = self.m_children.emplace(
+ child,
+ command_tree_node(&self, child));
+
+ // Give the child a dummy command.
+ auto path = self.m_parent != nullptr
+ ? std::format("{} {}", self.m_parent->path(), child)
+ : std::string(child);
+
+ it->second.m_command = std::make_shared<command_node>(path);
+
+ return &it->second;
+}
+
+auto command_tree_node::command(this command_tree_node const &self)
+ -> std::shared_ptr<command_node> const &
+{
+ return self.m_command;
+}
+
+auto command_tree_node::command(this command_tree_node &self,
+ std::shared_ptr<command_node> command)
+ -> void
+{
+ // TODO: Put this check back without tripping from the dummy command.
+ //if (self.m_command != nullptr)
+ // throw std::logic_error("duplicate command");
+ self.m_command = std::move(command);
+}
+
+auto command_tree_node::print_commands(this command_tree_node const &self)
+ -> void
+{
+ auto prefix = std::string(self.path());
+
+ for (auto &&[name, node] : self.m_children) {
+ auto command = prefix.empty()
+ ? name
+ : (prefix + ' ' + name);
+ std::print(std::cerr, " {}\n", command);
+ }
+}
+
+auto command_tree_node::path(this command_tree_node const &self)
+ -> std::string_view
+{
+ return self.m_command->path();
+}
+
+auto command_tree::insert(this command_tree &self,
+ std::vector<std::string_view> const &path,
+ std::shared_ptr<command_node> command)
+ -> void
+{
+ auto *this_node = &self.m_root_node;
+
+ // Find the node for this key.
+ for (auto &&this_word : path)
+ this_node = this_node->get_or_create_child(this_word);
+
+ // Set the new value.
+ this_node->command(std::move(command));
+}
+
+auto command_tree::find(this command_tree const &self, int &argc, char **&argv)
+ -> command_tree_node const *
+{
+ auto *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]);
+
+ if (next_node == nullptr)
+ return this_node;
+
+ this_node = next_node;
+
+ --argc;
+ ++argv;
+ }
+
+ // We ran out of path without finding a valid command. Return this
+ // node; the caller will notice the missing command.
+ return this_node;
+}
+
+auto build_command_tree() -> command_tree
+{
+ auto const &commands = get_registered_commands();
+ auto tree = command_tree();
+
+ for (auto &&command : commands) {
+ auto split_path = std::vector<std::string_view>(
+ std::from_range,
+ command->path()
+ | std::views::split(' ')
+ | std::views::transform([] (auto &&r) {
+ return std::string_view(r);
+ }));
+
+ // Throws std::logic_error on duplicates.
+ tree.insert(split_path, command);
+ }
+
+ return tree;
+}
+
+} // namespace nihil
diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm
new file mode 100644
index 0000000..7297af7
--- /dev/null
+++ b/nihil.cli/command_tree.ccm
@@ -0,0 +1,107 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <ranges>
+#include <vector>
+
+export module nihil.cli:command_tree;
+
+import :command;
+
+namespace nihil {
+
+/*
+ * command_tree_node represents a possibly-empty node in the command tree.
+ * For example, if two commands "add foo" and "add bar" are defined,
+ * then "add" will be implicitly created as an empty node.
+ */
+struct command_tree_node final {
+ command_tree_node();
+
+ command_tree_node(command_tree_node *parent,
+ std::string_view this_word);
+
+ command_tree_node(command_tree_node *parent,
+ std::string_view this_word,
+ std::shared_ptr<command_node> command);
+
+ /*
+ * Return a child node, or NULL if the child doesn't exist.
+ */
+ [[nodiscard]] auto get_child(this command_tree_node const &self,
+ std::string_view child)
+ -> command_tree_node const *;
+
+ [[nodiscard]] auto get_child(this command_tree_node &self,
+ std::string_view child)
+ -> command_tree_node *;
+
+ /*
+ * Return a child node if it exists, or insert a new empty node.
+ */
+ [[nodiscard]] auto get_or_create_child(this command_tree_node &self,
+ std::string_view child)
+ -> command_tree_node *;
+
+ /*
+ * Set or get this node's command.
+ */
+ [[nodiscard]] auto command(this command_tree_node const &self)
+ -> std::shared_ptr<command_node> const &;
+ auto command(this command_tree_node &self,
+ std::shared_ptr<command_node>)
+ -> void;
+
+ /*
+ * Get the path of this command_node.
+ */
+ [[nodiscard]] auto path(this command_tree_node const &self)
+ -> std::string_view;
+
+ /*
+ * Print this node's children in a form useful to humans.
+ */
+ auto print_commands(this command_tree_node const &self) -> void;
+
+private:
+ command_tree_node *m_parent = nullptr;
+ std::string m_this_word;
+ std::shared_ptr<command_node> m_command;
+ std::map<std::string, command_tree_node>
+ m_children;
+};
+
+/*
+ * The command tree stores commands in a tree structure suitable for searching.
+ */
+struct command_tree {
+ /*
+ * Add a node to the tree. Returns false if the node already exists.
+ */
+ auto insert(this command_tree &self,
+ std::vector<std::string_view> const &path,
+ std::shared_ptr<command_node> command)
+ -> void;
+
+ /*
+ * Find a node in the tree.
+ */
+ auto find(this command_tree const &self, int &argc, char **&argv)
+ -> command_tree_node const *;
+
+private:
+ command_tree_node m_root_node;
+};
+
+/*
+ * Build a command tree from the registry.
+ */
+[[nodiscard]] auto build_command_tree() -> command_tree;
+
+} // namespace nihil
diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc
new file mode 100644
index 0000000..736e16e
--- /dev/null
+++ b/nihil.cli/dispatch_command.cc
@@ -0,0 +1,82 @@
+/*
+ * 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>
+
+module nihil.cli;
+
+import nihil.core;
+
+namespace nihil {
+
+auto dispatch_command(int argc, char **argv) -> int
+{
+ auto tree = build_command_tree();
+
+ // The caller should have stripped argv[0] already. find() will
+ // strip all the remaining elements except the last, which means
+ // argv[0] will be set to something reasonable for the next call
+ // to getopt().
+
+ // find() never returns nullptr; at worst it will return the
+ // root node.
+ auto const *node = tree.find(argc, argv);
+
+ // Get the command_node.
+ auto const &command = node->command();
+
+ // Reset getopt(3) for the command, in case main() used it already.
+ optreset = 1;
+ optind = 1;
+
+ /*
+ * 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 cprogname = std::format("{} {}", ::getprogname(),
+ command->path());
+ ::setprogname(cprogname.c_str());
+ }
+
+ // Invoke the command see what it returns.
+ auto ret = command->invoke(argc, argv);
+
+ // Restore the old progname.
+ ::setprogname(old_progname);
+
+ // If the command produced an exit code, return it.
+ if (ret)
+ return *ret;
+
+ /*
+ * We have special handling for some errors.
+ */
+
+ // Incomplete command: print the list of valid commands at this node.
+ if (ret.error() == errc::incomplete_command) {
+ std::print(std::cerr, "{}: usage:\n", ::getprogname());
+ node->print_commands();
+ return 1;
+ }
+
+ // We didn't recognise the error, so just print it and exit.
+ std::print(std::cerr, "{}\n", ret.error());
+ return 1;
+}
+
+} // namespace nihil
diff --git a/nihil.cli/dispatch_command.ccm b/nihil.cli/dispatch_command.ccm
new file mode 100644
index 0000000..1ba55bb
--- /dev/null
+++ b/nihil.cli/dispatch_command.ccm
@@ -0,0 +1,31 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <functional>
+#include <iostream>
+#include <map>
+#include <string>
+#include <utility>
+
+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().
+ */
+export [[nodiscard]] auto dispatch_command(int argc, char **argv) -> int;
+
+} // namespace nihil
diff --git a/nihil.cli/nihil.cli.ccm b/nihil.cli/nihil.cli.ccm
new file mode 100644
index 0000000..6d98c05
--- /dev/null
+++ b/nihil.cli/nihil.cli.ccm
@@ -0,0 +1,14 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil.cli;
+
+export import :command;
+export import :command_tree;
+export import :command_node;
+export import :dispatch_command;
+export import :registry;
+export import :usage_error;
diff --git a/nihil.cli/registry.cc b/nihil.cli/registry.cc
new file mode 100644
index 0000000..e35078d
--- /dev/null
+++ b/nihil.cli/registry.cc
@@ -0,0 +1,57 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cstdio>
+#include <exception>
+#include <memory>
+#include <vector>
+
+module nihil.cli;
+
+namespace nihil {
+
+/*
+ * Get the registry storage. Because this is called from global ctors,
+ * it handles exceptions itself.
+ */
+auto get_registry() noexcept -> std::vector<std::shared_ptr<command_node>> &
+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);
+} catch (...) {
+ std::printf("get_registered_commands(): unknown error\n");
+ std::exit(1);
+}
+
+/*
+ * Register a new command.
+ */
+auto register_command(command *cmd) noexcept -> void
+try {
+ auto null_deleter = [] (command_node const *) -> void {};
+
+ auto &commands = get_registry();
+ commands.emplace_back(cmd, null_deleter);
+} catch (std::exception const &exc) {
+ std::printf("%s\n", exc.what());
+ std::exit(1);
+} catch (...) {
+ std::printf("get_registered_commands(): unknown error\n");
+ std::exit(1);
+}
+
+/*
+ * Get the list of registered commands.
+ */
+auto get_registered_commands() -> std::span<std::shared_ptr<command_node>>
+{
+ return {get_registry()};
+}
+
+} // namespace nihil
diff --git a/nihil.cli/registry.ccm b/nihil.cli/registry.ccm
new file mode 100644
index 0000000..0b9754d
--- /dev/null
+++ b/nihil.cli/registry.ccm
@@ -0,0 +1,28 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <memory>
+#include <span>
+
+export module nihil.cli:registry;
+
+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.
+ */
+auto register_command(command *cmd) noexcept -> void;
+
+/*
+ * 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
new file mode 100644
index 0000000..c82281e
--- /dev/null
+++ b/nihil.cli/test.cc
@@ -0,0 +1,104 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <iostream>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.cli;
+import nihil.util;
+
+namespace {
+
+auto cmd_sub1_called = false;
+auto cmd_sub1 = nihil::command("cmd sub1", "", [](int, char **) -> int
+{
+ cmd_sub1_called = true;
+ return 0;
+});
+
+auto cmd_sub2_called = false;
+auto cmd_sub2 = nihil::command("cmd sub2", "", [](int, char **) -> int
+{
+ cmd_sub2_called = true;
+ return 0;
+});
+
+} // anonymous namespace
+
+TEST_CASE("nihil.cli: dispatch_command: basic", "[nihil.cli]")
+{
+ SECTION("cmd sub1") {
+ auto args = std::vector<char const *>{
+ "cmd", "sub1", nullptr
+ };
+ auto argv = const_cast<char **>(args.data());
+
+ int ret = nihil::dispatch_command(
+ static_cast<int>(args.size()) - 1, argv);
+ REQUIRE(ret == 0);
+ REQUIRE(cmd_sub1_called == true);
+ REQUIRE(cmd_sub2_called == false);
+ }
+
+ SECTION("cmd sub2") {
+ auto args = std::vector<char const *>{
+ "cmd", "sub2", nullptr
+ };
+ auto argv = const_cast<char **>(args.data());
+
+ int ret = nihil::dispatch_command(
+ static_cast<int>(args.size()) - 1, argv);
+ REQUIRE(ret == 0);
+ REQUIRE(cmd_sub2_called == true);
+ }
+}
+
+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 output = std::string();
+ auto ret = int{};
+ {
+ auto capture = nihil::capture_stream(std::cerr);
+ ret = nihil::dispatch_command(
+ static_cast<int>(args.size()) - 1, argv);
+ std::cerr.flush();
+ output = capture.str();
+ }
+
+ REQUIRE(ret == 1);
+
+ auto *progname = ::getprogname();
+ REQUIRE(output == std::format("{}: usage:\n cmd\n", progname));
+}
+
+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 output = std::string();
+ auto ret = int{};
+ {
+ auto capture = nihil::capture_stream(std::cerr);
+ ret = nihil::dispatch_command(
+ static_cast<int>(args.size()) - 1, argv);
+ std::cerr.flush();
+ output = capture.str();
+ }
+
+ REQUIRE(ret == 1);
+
+ auto *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
new file mode 100644
index 0000000..61feba7
--- /dev/null
+++ b/nihil.cli/usage_error.ccm
@@ -0,0 +1,22 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <string>
+
+export module nihil.cli:usage_error;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * Exception thrown to indicate invalid command-line arguments.
+ */
+export struct usage_error : error {
+ usage_error(std::string_view what) : error(what) {}
+};
+
+} // namespace nihil
diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt
new file mode 100644
index 0000000..8a52d3c
--- /dev/null
+++ b/nihil.config/CMakeLists.txt
@@ -0,0 +1,31 @@
+# This source code is released into the public domain.
+
+add_library(nihil.config STATIC)
+target_link_libraries(nihil.config PRIVATE
+ nihil.error
+ nihil.generator
+ nihil.posix
+ nihil.ucl
+)
+target_sources(nihil.config
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.config.ccm
+ read.ccm
+ store.ccm
+ write.ccm
+
+ option.ccm
+ string.ccm
+
+ PRIVATE
+ option.cc
+ read.cc
+ store.cc
+ string.cc
+ write.cc
+)
+
+if(NIHIL_TESTS)
+ add_subdirectory(tests)
+ enable_testing()
+endif()
diff --git a/nihil.config/nihil.config.ccm b/nihil.config/nihil.config.ccm
new file mode 100644
index 0000000..8957305
--- /dev/null
+++ b/nihil.config/nihil.config.ccm
@@ -0,0 +1,13 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil.config;
+
+export import :option;
+export import :read;
+export import :store;
+export import :string;
+export import :write;
diff --git a/nihil.config/option.cc b/nihil.config/option.cc
new file mode 100644
index 0000000..886f4b6
--- /dev/null
+++ b/nihil.config/option.cc
@@ -0,0 +1,97 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <coroutine>
+#include <expected>
+#include <iostream>
+#include <string>
+
+module nihil.config;
+
+import nihil.error;
+import nihil.monad;
+import nihil.ucl;
+
+namespace nihil::config {
+
+//NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+option::option(std::string_view name, std::string_view description)
+ : m_name(name)
+ , m_description(description)
+{
+ auto okay = store::get().register_option(this);
+ if (okay)
+ return;
+
+ std::print(std::cerr,
+ "INTERNAL ERROR: failed to register "
+ "configuration option '{}': {}",
+ m_name, okay.error());
+ std::exit(1);
+}
+
+option::~option()
+{
+ std::ignore = store::get().unregister_option(this);
+}
+
+auto option::name(this option const &self) noexcept
+ -> std::string_view
+{
+ return self.m_name;
+}
+
+auto option::description(this option const &self) noexcept
+ -> std::string_view
+{
+ return self.m_description;
+}
+
+auto option::is_default(this option const &self) noexcept
+ -> bool
+{
+ return self.m_is_default;
+}
+
+auto option::is_default(this option &self, bool b) -> void
+{
+ self.m_is_default = b;
+}
+
+auto option::string(this option const &self) -> std::string
+{
+ return self.get_string();
+}
+
+auto option::string(this option &self, std::string_view value)
+ -> std::expected<void, error>
+{
+ co_await self.set_string(value);
+ self.is_default(false);
+ co_return {};
+}
+
+auto option::ucl(this option const &self)
+ -> std::expected<nihil::ucl::object, error>
+{
+ return self.get_ucl();
+}
+
+auto option::ucl(this option &self, nihil::ucl::object const &value)
+ -> std::expected<void, error>
+{
+ co_await self.set_ucl(value);
+ self.is_default(false);
+ co_return {};
+}
+
+auto operator<<(std::ostream &strm, option const &opt)
+-> std::ostream &
+{
+ return strm << "<" << opt.name() << "=" << opt.string() << ">";
+}
+
+} // namespace nihil
diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm
new file mode 100644
index 0000000..4b95793
--- /dev/null
+++ b/nihil.config/option.ccm
@@ -0,0 +1,105 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <iosfwd>
+#include <string>
+
+export module nihil.config:option;
+
+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.
+ */
+
+export struct option
+{
+ virtual ~option();
+
+ // Short name of this option.
+ [[nodiscard]] auto name(this option const &) noexcept
+ -> std::string_view;
+
+ // Human-readable description of this option.
+ [[nodiscard]] auto description(this option const &) noexcept
+ -> std::string_view;
+
+ // If true, this option is set to its default value.
+ [[nodiscard]] auto is_default(this option const &) noexcept
+ -> bool;
+
+ /*
+ * Get or set this option as a string. The specific conversion
+ * method depends on the derived option type.
+ */
+ [[nodiscard]] auto string(this option const &) -> std::string;
+ [[nodiscard]] auto string(this option &, std::string_view value)
+ -> std::expected<void, error>;
+
+ /*
+ * Return this object as a UCL object. This is used when writing the
+ * configuration file.
+ */
+ [[nodiscard]] auto ucl(this option const &)
+ -> std::expected<ucl::object, error>;
+
+ /*
+ * Set this object from a UCL object. This is used when reading the
+ * configuration file.
+ */
+ [[nodiscard]] auto ucl(this option &, ucl::object const &)
+ -> std::expected<void, error>;
+
+ // Not copyable or movable.
+ option(option const &) = delete;
+ auto operator=(option const &) -> option& = delete;
+
+protected:
+ option(std::string_view name, std::string_view description);
+
+ auto is_default(this option &, bool) -> void;
+
+ /*
+ * Get or set this option as a string.
+ */
+ [[nodiscard]] virtual auto get_string() const
+ -> std::string = 0;
+ [[nodiscard]] virtual auto set_string(std::string_view)
+ -> std::expected<void, error> = 0;
+
+ /*
+ * Get or set this option as a UCL object.
+ */
+ [[nodiscard]] virtual auto get_ucl() const
+ -> std::expected<ucl::object, error> = 0;
+ [[nodiscard]] virtual auto set_ucl(ucl::object const &)
+ -> std::expected<void, error> = 0;
+
+private:
+ std::string m_name;
+ std::string m_description;
+ bool m_is_default = true;
+};
+
+/*
+ * Make options printable. This is mostly useful for testing.
+ */
+export auto operator<<(std::ostream &strm, option const &opt) -> std::ostream &;
+
+} // namespace nihil
diff --git a/nihil.config/read.cc b/nihil.config/read.cc
new file mode 100644
index 0000000..48484fb
--- /dev/null
+++ b/nihil.config/read.cc
@@ -0,0 +1,50 @@
+/*
+ * 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
new file mode 100644
index 0000000..9cf28c9
--- /dev/null
+++ b/nihil.config/read.ccm
@@ -0,0 +1,22 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+export module nihil.config:read;
+
+import nihil.error;
+
+namespace nihil::config {
+
+/*
+ * Load the configuration from a file.
+ */
+export [[nodiscard]] auto read_from(std::filesystem::path const &filename)
+ -> std::expected<void, error>;
+
+} // namespace nihil::config
diff --git a/nihil.config/store.cc b/nihil.config/store.cc
new file mode 100644
index 0000000..0fb8cc0
--- /dev/null
+++ b/nihil.config/store.cc
@@ -0,0 +1,95 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <coroutine>
+#include <expected>
+#include <filesystem>
+#include <format>
+#include <map>
+
+module nihil.config;
+
+import nihil.error;
+import nihil.generator;
+import nihil.monad;
+
+namespace nihil::config {
+
+store::store() = default;
+
+auto store::get() -> store &
+{
+ static auto instance = store();
+ return instance;
+}
+
+
+auto store::register_option(this store &self, option *object)
+ -> std::expected<void, error>
+{
+ auto [it, okay] = self.m_options.insert(
+ std::pair{object->name(), object});
+
+ if (okay)
+ return {};
+
+ return std::unexpected(error(std::format(
+ "attempt to register duplicate "
+ "configuration option '{0}'",
+ object->name())));
+}
+
+auto store::unregister_option(this store &self, option *object)
+ -> std::expected<void, error>
+{
+ auto it = self.m_options.find(object->name());
+ if (it == self.m_options.end())
+ return std::unexpected(error(std::format(
+ "attempt to unregister non-existent "
+ "configuration option '{}'",
+ object->name())));
+
+ self.m_options.erase(it);
+ return {};
+}
+
+auto store::fetch(this store const &self, std::string_view name)
+ -> std::expected<option const *, error>
+{
+ if (auto it = self.m_options.find(name); it != self.m_options.end())
+ return it->second;
+
+ return std::unexpected(error(std::format(
+ "unknown configuration option '{}'",
+ name)));
+}
+
+auto store::fetch(this store &self, std::string_view name)
+ -> std::expected<option *, error>
+{
+ auto opt = co_await static_cast<store const &>(self).fetch(name);
+ co_return const_cast<option *>(opt);
+}
+
+auto store::all(this store const &self) -> nihil::generator<option const *>
+{
+ for (auto &&it : self.m_options)
+ co_yield it.second;
+}
+
+auto store::all(this store &self) -> nihil::generator<option *>
+{
+ for (auto &&it : self.m_options)
+ co_yield it.second;
+}
+
+auto get_option(std::string_view option_name)
+ -> std::expected<option *, error>
+{
+ co_return co_await store::get().fetch(option_name);
+}
+
+} // namespace nihil::config
diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm
new file mode 100644
index 0000000..4d37ce0
--- /dev/null
+++ b/nihil.config/store.ccm
@@ -0,0 +1,75 @@
+/*
+ * 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>
+
+export module nihil.config:store;
+
+import nihil.generator;
+import :option;
+
+namespace nihil::config {
+
+struct store final {
+ /*
+ * Get the global config store.
+ */
+ [[nodiscard]] static auto get() -> store &;
+
+ /*
+ * Register a new value with the config store.
+ */
+ [[nodiscard]] auto register_option(this store &, option *object)
+ -> std::expected<void, error>;
+
+ /*
+ * Remove a value from the config store.
+ */
+ [[nodiscard]] auto unregister_option(this store &, option *object)
+ -> std::expected<void, error>;
+
+ /*
+ * Fetch an existing value in the config store.
+ */
+ [[nodiscard]] auto fetch(this store const &, std::string_view name)
+ -> std::expected<option const *, error>;
+ [[nodiscard]] auto fetch(this store &, std::string_view name)
+ -> std::expected<option *, error>;
+
+ /*
+ * Fetch all values in the configuration store.
+ */
+ [[nodiscard]] auto all(this store const &self)
+ -> nihil::generator<option const *>;
+ [[nodiscard]] auto all(this store &self)
+ -> nihil::generator<option *>;
+
+ // Not movable or copyable.
+ store(store const &) = delete;
+ store(store &&) = delete;
+ store& operator=(store const &) = delete;
+ store& operator=(store &&) = delete;
+
+private:
+ store();
+
+ std::map<std::string_view, option *> m_options;
+};
+
+/*
+ * The public API.
+ */
+export auto get_option(std::string_view option_name)
+ -> std::expected<option *, error>;
+
+} // namespace nihil::config
diff --git a/nihil.config/string.cc b/nihil.config/string.cc
new file mode 100644
index 0000000..0ca4605
--- /dev/null
+++ b/nihil.config/string.cc
@@ -0,0 +1,62 @@
+/*
+ * 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
new file mode 100644
index 0000000..668bbc0
--- /dev/null
+++ b/nihil.config/string.ccm
@@ -0,0 +1,56 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <format>
+#include <string>
+
+export module nihil.config:string;
+
+import nihil.ucl;
+import :option;
+
+namespace nihil::config {
+
+/*
+ * 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;
+
+private:
+ std::string &m_storage;
+};
+
+} // namespace nihil::config
diff --git a/nihil.config/tests/CMakeLists.txt b/nihil.config/tests/CMakeLists.txt
new file mode 100644
index 0000000..1805f7f
--- /dev/null
+++ b/nihil.config/tests/CMakeLists.txt
@@ -0,0 +1,15 @@
+# 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)
+
+find_package(Catch2 REQUIRED)
+
+include(CTest)
+include(Catch)
+catch_discover_tests(nihil.config.test)
diff --git a/nihil.config/tests/string.cc b/nihil.config/tests/string.cc
new file mode 100644
index 0000000..aeb1ef8
--- /dev/null
+++ b/nihil.config/tests/string.cc
@@ -0,0 +1,36 @@
+/*
+ * 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
new file mode 100644
index 0000000..80125a8
--- /dev/null
+++ b/nihil.config/write.cc
@@ -0,0 +1,41 @@
+/*
+ * 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
new file mode 100644
index 0000000..564bb20
--- /dev/null
+++ b/nihil.config/write.ccm
@@ -0,0 +1,22 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+export module nihil.config:write;
+
+import nihil.error;
+
+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>;
+
+};
diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt
new file mode 100644
index 0000000..2a7b3e2
--- /dev/null
+++ b/nihil.core/CMakeLists.txt
@@ -0,0 +1,12 @@
+# This source code is released into the public domain.
+
+add_library(nihil.core STATIC)
+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
+
+ PRIVATE
+ errc.cc
+)
diff --git a/nihil.core/errc.cc b/nihil.core/errc.cc
new file mode 100644
index 0000000..35c9d8f
--- /dev/null
+++ b/nihil.core/errc.cc
@@ -0,0 +1,51 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <string>
+#include <system_error>
+
+module nihil.core;
+
+namespace nihil {
+
+struct nihil_error_category final : std::error_category {
+ [[nodiscard]] auto name() const noexcept -> char const * override;
+ [[nodiscard]] auto message(int err) const -> std::string override;
+};
+
+[[nodiscard]] auto nihil_category() noexcept -> std::error_category &
+{
+ static auto category = nihil_error_category();
+ return category;
+}
+
+auto make_error_condition(errc ec) -> std::error_condition
+{
+ return {static_cast<int>(ec), nihil_category()};
+}
+
+auto nihil_error_category::name() const noexcept -> char const *
+{
+ return "nihil";
+}
+
+auto nihil_error_category::message(int err) const -> std::string
+{
+ switch (static_cast<errc>(err)) {
+ case errc::no_error:
+ return "No error";
+ case errc::incomplete_command:
+ return "Incomplete command";
+ case errc::empty_string:
+ return "Empty string is not permitted";
+ case errc::invalid_unit:
+ return "Invalid unit specifier";
+ default:
+ return "Undefined error";
+ }
+}
+
+} // namespace nihil
diff --git a/nihil.core/errc.ccm b/nihil.core/errc.ccm
new file mode 100644
index 0000000..c597faf
--- /dev/null
+++ b/nihil.core/errc.ccm
@@ -0,0 +1,44 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <string>
+#include <system_error>
+
+export module nihil.core:errc;
+
+namespace nihil {
+
+export enum struct errc {
+ no_error = 0,
+
+ /*
+ * nihil.command
+ */
+
+ incomplete_command,
+
+ /*
+ * nihil.util
+ */
+
+ // Empty string is not allowed.
+ empty_string,
+
+ // Invalid unit, e.g. in parse_size()
+ invalid_unit,
+};
+
+export [[nodiscard]] auto nihil_category() noexcept -> std::error_category &;
+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
diff --git a/nihil.core/nihil.core.ccm b/nihil.core/nihil.core.ccm
new file mode 100644
index 0000000..a7a4100
--- /dev/null
+++ b/nihil.core/nihil.core.ccm
@@ -0,0 +1,9 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil.core;
+
+export import :errc;
diff --git a/nihil.core/nihil.hh b/nihil.core/nihil.hh
new file mode 100644
index 0000000..cd7e789
--- /dev/null
+++ b/nihil.core/nihil.hh
@@ -0,0 +1,26 @@
+/*
+* This source code is released into the public domain.
+ */
+
+#ifndef NIHIL_HH_INCLUDED
+#define NIHIL_HH_INCLUDED
+
+#if __has_include(<sys/param.h>)
+# include <sys/param.h>
+#endif
+
+#if defined(__FreeBSD_version)
+
+/* fexecve() added in FreeBSD 8.0 */
+#if (__FreeBSD_version >= 800000)
+# define NIHIL_HAVE_FEXECVE
+#endif
+
+/* getenv_r() added in FreeBSD 15.0 */
+#if (__FreeBSD_version >= 1500000)
+# define NIHIL_HAVE_GETENV_R
+#endif
+
+#endif // defined(__FreeBSD_version)
+
+#endif // !NIHIL_HH_INCLUDED
diff --git a/nihil.error/CMakeLists.txt b/nihil.error/CMakeLists.txt
new file mode 100644
index 0000000..1316b71
--- /dev/null
+++ b/nihil.error/CMakeLists.txt
@@ -0,0 +1,29 @@
+# This source code is released into the public domain.
+
+add_library(nihil.error STATIC)
+target_link_libraries(nihil.error PRIVATE nihil.match)
+target_sources(nihil.error
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ error.ccm
+
+ PRIVATE
+ error.cc
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.error.test
+ test.cc)
+
+ target_link_libraries(nihil.error.test PRIVATE
+ nihil.error
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.error.test)
+endif()
diff --git a/nihil.error/error.cc b/nihil.error/error.cc
new file mode 100644
index 0000000..e4023f9
--- /dev/null
+++ b/nihil.error/error.cc
@@ -0,0 +1,160 @@
+/*
+ * 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
new file mode 100644
index 0000000..12d47cc
--- /dev/null
+++ b/nihil.error/error.ccm
@@ -0,0 +1,199 @@
+/*
+ * 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>
+
+export module nihil.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 {
+ // Create an empty error, representing success.
+ error();
+
+ // Destroy an error.
+ ~error() override;
+
+ // Create an error from a freeform string.
+ error(std::string_view what, error cause);
+ explicit error(std::string_view what);
+
+ template<typename Cause>
+ requires(std::is_error_code_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_code.
+ error(std::error_condition what, error cause);
+ explicit error(std::error_condition 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 enum.
+ error(auto errc, error cause)
+ requires(std::is_error_code_enum<decltype(errc)>::value)
+ : error(make_error_code(errc), std::move(cause))
+ {}
+
+ 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_condition enum.
+ 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;
+
+ auto operator=(this error &, error const &) -> error &;
+ auto operator=(this error &, error &&) noexcept -> error &;
+
+ // Return the cause of this error.
+ [[nodiscard]] auto cause(this error const &) -> std::shared_ptr<error>;
+
+ // 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 &;
+
+ // Format this error as a string.
+ [[nodiscard]] auto str(this error const &) -> std::string;
+
+ // Return this error's error_code, if any.
+ [[nodiscard]] auto code(this error const &)
+ -> std::optional<std::error_code>;
+
+ // Return this error's error_condition, if any.
+ [[nodiscard]] auto condition(this error const &)
+ -> std::optional<std::error_condition>;
+
+ [[nodiscard]] auto what() const noexcept -> char const * final;
+
+private:
+ friend auto operator==(error const &, error const &) -> bool;
+ friend auto operator<=>(error const &, error const &)
+ -> std::strong_ordering;
+
+ // This error.
+ error_t m_error = make_error_code(std::errc());
+
+ // The cause of this error, if any.
+ std::shared_ptr<error> m_cause;
+
+ // 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;
+}
+
+// Print an error to an ostream.
+export [[nodiscard]] auto operator<<(std::ostream &, error const &)
+ -> std::ostream &;
+
+} // namespace nihil
+
+// Make error formattable.
+export template<>
+struct std::formatter<nihil::error, char>
+{
+ 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
+ {
+ return std::ranges::copy(to_string(e), ctx.out()).out;
+ }
+};
diff --git a/nihil.error/test.cc b/nihil.error/test.cc
new file mode 100644
index 0000000..9b3eef1
--- /dev/null
+++ b/nihil.error/test.cc
@@ -0,0 +1,169 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <cerrno>
+#include <cstring>
+#include <system_error>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.error;
+
+TEST_CASE("error: invariants", "[nihil]")
+{
+ static_assert(std::destructible<nihil::error>);
+ static_assert(std::default_initializable<nihil::error>);
+ static_assert(std::move_constructible<nihil::error>);
+ static_assert(std::copy_constructible<nihil::error>);
+ static_assert(std::equality_comparable<nihil::error>);
+ static_assert(std::totally_ordered<nihil::error>);
+ static_assert(std::swappable<nihil::error>);
+ static_assert(std::regular<nihil::error>);
+}
+
+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));
+}
+
+TEST_CASE("error: construct from std::error_condition", "[nihil]")
+{
+ using namespace nihil;
+
+ auto code = std::make_error_condition(std::errc::invalid_argument);
+ auto e = error(code);
+
+ REQUIRE(!e.cause());
+ REQUIRE(e.code().has_value() == false);
+ REQUIRE(e.condition().has_value() == true);
+
+ 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));
+}
+
+TEST_CASE("error: construct from std::errc", "[nihil]")
+{
+ using namespace nihil;
+
+ auto e = error(std::errc::invalid_argument);
+
+ REQUIRE(!e.cause());
+ REQUIRE(e.code().has_value() == false);
+ REQUIRE(e.condition().has_value() == true);
+
+ 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));
+}
+
+TEST_CASE("error: compound error", "[nihil]")
+{
+ using namespace std::literals;
+ using namespace nihil;
+
+ auto e = error("cannot open file",
+ error(std::errc::no_such_file_or_directory));
+
+ REQUIRE(e.cause());
+ REQUIRE(e.code().has_value() == false);
+ 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));
+}
+
+TEST_CASE("error: operator== with strings", "[nihil]")
+{
+ using namespace nihil;
+
+ auto e1 = error("error");
+ auto e2 = error("error");
+ auto e3 = error("an error");
+
+ REQUIRE(e1 == e2);
+ REQUIRE(e1 != e3);
+}
+
+TEST_CASE("error: operator< with strings", "[nihil]")
+{
+ using namespace nihil;
+
+ auto e1 = error("aaa");
+ auto e2 = error("zzz");
+
+ REQUIRE(e1 < e2);
+}
+
+TEST_CASE("error: operator== with a cause", "[nihil]")
+{
+ using namespace nihil;
+
+ auto e1 = error("error", error("cause 1"));
+ auto e2 = error("error", error("cause 2"));
+
+ REQUIRE(e1 == e2);
+}
+
+TEST_CASE("error: operator== with error_conditions", "[nihil]")
+{
+ using namespace nihil;
+
+ auto e1 = error(std::errc::invalid_argument);
+ auto e2 = error(std::errc::invalid_argument);
+ auto e3 = error(std::errc::permission_denied);
+
+ REQUIRE(e1 == e2);
+ REQUIRE(e1 != e3);
+}
+
+TEST_CASE("error: std::format with string", "[nihil]")
+{
+ using namespace nihil;
+
+ auto err = error("an error");
+ REQUIRE(std::format("{}", err) == "an error");
+}
+
+TEST_CASE("error: std::format with std::errc", "[nihil]")
+{
+ using namespace nihil;
+
+ auto err = error(std::errc::invalid_argument);
+ REQUIRE(std::format("{}", err) == std::strerror(EINVAL));
+}
+
+TEST_CASE("error: std::format with cause", "[nihil]")
+{
+ using namespace nihil;
+
+ auto err = error("an error", std::errc::invalid_argument);
+ REQUIRE(std::format("{}", err) == "an error: Invalid argument");
+}
+
+TEST_CASE("error: throw and catch", "[nihil]")
+{
+ using namespace std::literals;
+ using namespace nihil;
+
+ try {
+ throw error("oh no", error(std::errc::invalid_argument));
+ } catch (std::exception const &exc) {
+ REQUIRE(exc.what() == "oh no: Invalid argument"s);
+ }
+}
diff --git a/nihil.flagset/CMakeLists.txt b/nihil.flagset/CMakeLists.txt
new file mode 100644
index 0000000..be9b99b
--- /dev/null
+++ b/nihil.flagset/CMakeLists.txt
@@ -0,0 +1,23 @@
+# This source code is released into the public domain.
+
+add_library(nihil.flagset STATIC)
+target_sources(nihil.flagset
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ flagset.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.flagset.test test.cc)
+ target_link_libraries(nihil.flagset.test PRIVATE
+ nihil.flagset
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.flagset.test)
+endif()
diff --git a/nihil.flagset/flagset.ccm b/nihil.flagset/flagset.ccm
new file mode 100644
index 0000000..8369b75
--- /dev/null
+++ b/nihil.flagset/flagset.ccm
@@ -0,0 +1,228 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * flagset: a type-type flags type.
+ */
+
+#include <concepts>
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <print>
+
+export module nihil.flagset;
+
+namespace nihil {
+
+export template<std::integral base_type, typename Tag>
+struct flagset final {
+ using underlying_type = base_type;
+
+ /*
+ * Create an empty flags.
+ */
+ flagset() noexcept = default;
+
+ /*
+ * Copyable.
+ */
+ flagset(flagset const &other) noexcept
+ : m_value(other.m_value)
+ {}
+
+ /*
+ * Create flags from an integer mask.
+ */
+ template<base_type flag>
+ [[nodiscard]] static constexpr auto mask() noexcept -> flagset
+ {
+ return flagset(flag);
+ }
+
+ /*
+ * Create flags for a specific bit.
+ */
+ template<unsigned bitnr>
+ [[nodiscard]] static constexpr auto bit() noexcept -> flagset
+ {
+ static_assert(bitnr < std::numeric_limits<base_type>::digits);
+ return flagset(static_cast<base_type>(1) << bitnr);
+ }
+
+ /*
+ * Create flags from a runtime value.
+ */
+ [[nodiscard]] static auto from_int(base_type value) noexcept
+ -> flagset
+ {
+ return flagset(value);
+ }
+
+ /*
+ * Assign this flagset.
+ */
+ auto operator=(this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ if (&lhs != &rhs)
+ lhs.m_value = rhs.m_value;
+ return lhs;
+ }
+
+ /*
+ * The integer value of this flagset.
+ */
+ [[nodiscard]] constexpr auto value(this flagset self) noexcept
+ -> base_type
+ {
+ return self.m_value;
+ }
+
+ /*
+ * True if this flagset has any bits set.
+ */
+ [[nodiscard]] explicit constexpr operator bool(this flagset self)
+ noexcept
+ {
+ return self.m_value != 0;
+ }
+
+ /*
+ * Set bits.
+ */
+ constexpr auto operator|= (this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ lhs.m_value |= rhs.value();
+ return lhs;
+ }
+
+ /*
+ * Mask bits.
+ */
+ constexpr auto operator&= (this flagset &lhs, flagset rhs) noexcept
+ -> flagset &
+ {
+ lhs.m_value &= rhs.value();
+ return lhs;
+ }
+
+ /*
+ * Invert bits.
+ */
+ [[nodiscard]] constexpr auto operator~ (this flagset self) noexcept
+ -> flagset
+ {
+ return flagset(~self.m_value);
+ }
+
+ /*
+ * xor bits.
+ */
+ constexpr auto operator^= (this flagset &lhs, flagset rhs)
+ noexcept -> flagset
+ {
+ lhs.m_value ^= rhs.value();
+ return lhs;
+ }
+
+private:
+ base_type m_value = 0;
+
+ explicit constexpr flagset(base_type mask) noexcept
+ : m_value(mask)
+ {}
+};
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator| (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs |= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator& (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs &= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator^ (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> flagset<base_type, Tag>
+{
+ return (lhs ^= rhs);
+}
+
+export template<std::integral base_type, typename Tag>
+[[nodiscard]] auto operator== (flagset<base_type, Tag> lhs,
+ flagset<base_type, Tag> rhs) noexcept
+ -> bool
+{
+ return lhs.value() == rhs.value();
+}
+
+export template<std::integral base_type, typename Tag>
+auto operator<<(std::ostream &strm, flagset<base_type, Tag> flags)
+ -> std::ostream &
+{
+ std::print(strm, "{}", flags);
+ return strm;
+}
+
+} // namespace nihil
+
+/*
+ * Formatting for flagset.
+ */
+export template<std::integral base_type, typename Tag, typename Char>
+struct std::formatter<nihil::flagset<base_type, Tag>, Char>
+{
+ using flags_t = nihil::flagset<base_type, Tag>;
+
+ template<typename ParseContext>
+ constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator
+ {
+ return ctx.begin();
+ }
+
+ template<typename FmtContext>
+ auto format(flags_t flags, FmtContext& ctx) const
+ -> FmtContext::iterator
+ {
+ auto constexpr digits = std::numeric_limits<base_type>::digits;
+ auto value = flags.value();
+ auto it = ctx.out();
+ *it++ = Char{'<'};
+
+ auto printed_any = false;
+
+ for (unsigned i = 0; i < digits; ++i) {
+ unsigned bit = (digits - 1) - i;
+
+ auto this_bit = static_cast<base_type>(1) << bit;
+ if ((value & this_bit) == 0)
+ continue;
+
+ if (printed_any)
+ *it++ = Char{','};
+
+ if (bit > 10)
+ *it++ = Char{'0'} + (bit / 10);
+ *it++ = Char{'0'} + (bit % 10);
+
+ printed_any = true;
+ }
+
+ *it++ = Char{'>'};
+ return it;
+ }
+};
diff --git a/nihil.flagset/test.cc b/nihil.flagset/test.cc
new file mode 100644
index 0000000..c3ebd35
--- /dev/null
+++ b/nihil.flagset/test.cc
@@ -0,0 +1,149 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <format>
+#include <sstream>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.flagset;
+
+namespace {
+
+struct test_tag{};
+using testflags = nihil::flagset<unsigned, test_tag>;
+
+constexpr auto zero = testflags::bit<0>();
+constexpr auto one = testflags::bit<1>();
+constexpr auto two = testflags::bit<2>();
+constexpr auto twelve = testflags::bit<12>();
+
+}
+
+TEST_CASE("flagset: invariant", "[nihil]")
+{
+ static_assert(std::regular<testflags>);
+ static_assert(sizeof(testflags) == sizeof(testflags::underlying_type));
+}
+
+TEST_CASE("flagset: bit<>", "[nihil]")
+{
+ REQUIRE(zero.value() == 0x1);
+ REQUIRE(one.value() == 0x2);
+}
+
+TEST_CASE("flagset: mask<>", "[nihil]")
+{
+ auto zero_ = testflags::mask<0x1>();
+ auto one_ = testflags::mask<0x2>();
+
+ REQUIRE(zero_ == zero);
+ REQUIRE(one_ == one);
+}
+
+TEST_CASE("flagset: constructor", "[nihil]")
+{
+ SECTION("default construct") {
+ auto flags = testflags();
+ REQUIRE(flags.value() == 0);
+ }
+
+ SECTION("construct from int") {
+ auto flags = testflags::from_int(one.value() | zero.value());
+ REQUIRE(flags == (one | zero));
+ }
+
+ SECTION("copy construct") {
+ auto flags = one;
+ auto flags2(flags);
+ REQUIRE(flags == flags2);
+ }
+}
+
+TEST_CASE("flagset: operators", "[nihil]")
+{
+ SECTION("operator|") {
+ REQUIRE((zero | one).value() == 0x3);
+ }
+
+ SECTION("operator|=") {
+ auto flags = zero;
+ flags |= one;
+ REQUIRE(flags.value() == 0x3);
+ }
+
+ SECTION("operator&") {
+ auto flags = zero | one;
+
+ REQUIRE((flags & zero) == zero);
+ }
+
+ SECTION("operator&=") {
+ auto flags = zero | one | two;
+ REQUIRE(flags.value() == 0x7);
+ flags &= (zero | one);
+ REQUIRE(flags.value() == 0x3);
+ }
+
+ SECTION("operator^") {
+ auto flags = zero | one;
+ REQUIRE((flags ^ (one | two)) == (zero | two));
+ }
+
+ SECTION("operator^=") {
+ auto flags = zero | one;
+ flags ^= (one | two);
+ REQUIRE(flags == (zero | two));
+ }
+
+ SECTION("operator~") {
+ auto flags = ~zero;
+ REQUIRE(flags.value() == ~static_cast<unsigned>(1));
+ }
+
+ SECTION("operator==") {
+ auto flags = zero;
+ REQUIRE(flags == zero);
+ REQUIRE(flags != one);
+ }
+}
+
+TEST_CASE("flagset: assignment", "[nihil]")
+{
+ auto flags = zero;
+ REQUIRE(flags == zero);
+
+ flags = one;
+ REQUIRE(flags == one);
+ REQUIRE(flags != zero);
+}
+
+TEST_CASE("flagset: format", "[nihil]")
+{
+ REQUIRE(std::format("{}", testflags()) == "<>");
+ REQUIRE(std::format("{}", zero) == "<0>");
+ REQUIRE(std::format("{}", one) == "<1>");
+ REQUIRE(std::format("{}", zero | one) == "<1,0>");
+
+ REQUIRE(std::format("{}", twelve) == "<12>");
+ REQUIRE(std::format("{}", twelve | one) == "<12,1>");
+}
+
+TEST_CASE("flagset: ostream operator<<", "[nihil]")
+{
+ auto write = [] (testflags flags) -> std::string {
+ auto strm = std::ostringstream();
+ strm << flags;
+ return strm.str();
+ };
+
+ REQUIRE(write(testflags()) == "<>");
+ REQUIRE(write(zero) == "<0>");
+ REQUIRE(write(one) == "<1>");
+ REQUIRE(write(zero | one) == "<1,0>");
+
+ REQUIRE(write(twelve) == "<12>");
+ REQUIRE(write(twelve | one) == "<12,1>");
+}
diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt
new file mode 100644
index 0000000..56afdac
--- /dev/null
+++ b/nihil.generator/CMakeLists.txt
@@ -0,0 +1,29 @@
+# This source code is released into the public domain.
+
+add_library(nihil.generator STATIC)
+target_sources(nihil.generator
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.generator.ccm
+
+ generator.ccm
+ elements_of.ccm
+ manual_lifetime.ccm
+ promise_base_alloc.ccm
+ util.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.generator.test test.cc)
+ target_link_libraries(nihil.generator.test PRIVATE
+ nihil.generator
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.generator.test)
+endif()
diff --git a/nihil.generator/elements_of.ccm b/nihil.generator/elements_of.ccm
new file mode 100644
index 0000000..0e34eb9
--- /dev/null
+++ b/nihil.generator/elements_of.ccm
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <concepts>
+
+export module nihil.generator:elements_of;
+
+import :util;
+
+namespace nihil {
+
+export template <typename Range, typename Allocator = use_allocator_arg>
+struct elements_of {
+ explicit constexpr elements_of(Range &&range) noexcept
+ requires std::is_default_constructible_v<Allocator>
+ : m_range(static_cast<Range &&>(range))
+ {
+ }
+
+ constexpr elements_of(Range &&range, Allocator &&alloc) noexcept
+ : m_range(static_cast<Range &&>(range))
+ , m_alloc(static_cast<Allocator &&>(alloc))
+ {}
+
+ constexpr elements_of(elements_of &&) noexcept = default;
+
+ constexpr elements_of(const elements_of &) = delete;
+
+ constexpr auto operator=(this elements_of &, const elements_of &)
+ -> elements_of & = delete;
+ constexpr auto operator=(this elements_of &, elements_of &&) noexcept
+ -> elements_of & = delete;
+
+ [[nodiscard]] constexpr auto
+ get(this elements_of const &self) noexcept -> Range &&
+ {
+ return static_cast<Range &&>(self.m_range);
+ }
+
+ [[nodiscard]] constexpr auto
+ get_allocator(this elements_of const &self) noexcept -> Allocator
+ {
+ return self.m_alloc;
+ }
+
+private:
+ [[no_unique_address]] Allocator m_alloc;
+ Range &&m_range;
+};
+
+export template <typename Range>
+elements_of(Range &&) -> elements_of<Range>;
+
+export template <typename Range, typename Allocator>
+elements_of(Range &&, Allocator &&) -> elements_of<Range, Allocator>;
+
+} // namespace nihil
diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm
new file mode 100644
index 0000000..27e8103
--- /dev/null
+++ b/nihil.generator/generator.ccm
@@ -0,0 +1,507 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <cassert>
+#include <coroutine>
+#include <exception>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+export module nihil.generator:generator;
+
+import :elements_of;
+import :manual_lifetime;
+import :promise_base_alloc;
+import :util;
+
+namespace nihil {
+
+export template <typename _Ref,
+ typename _Value = std::remove_cvref_t<_Ref>,
+ typename _Allocator = use_allocator_arg>
+class generator;
+
+
+template<typename _Ref>
+struct __generator_promise_base
+{
+ template <typename _Ref2, typename _Value, typename _Alloc>
+ friend class generator;
+
+ __generator_promise_base* __root_;
+ std::coroutine_handle<> __parentOrLeaf_;
+ // Note: Using manual_lifetime here to avoid extra calls to exception_ptr
+ // constructor/destructor in cases where it is not needed (i.e. where this
+ // generator coroutine is not used as a nested coroutine).
+ // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend()
+ // method if this generator is used as a nested generator.
+ manual_lifetime<std::exception_ptr> __exception_;
+ manual_lifetime<_Ref> __value_;
+
+ explicit __generator_promise_base(std::coroutine_handle<> thisCoro) noexcept
+ : __root_(this)
+ , __parentOrLeaf_(thisCoro)
+ {}
+
+ ~__generator_promise_base() {
+ if (__root_ != this) {
+ // This coroutine was used as a nested generator and so will
+ // have constructed its __exception_ member which needs to be
+ // destroyed here.
+ __exception_.destruct();
+ }
+ }
+
+ std::suspend_always initial_suspend() noexcept {
+ return {};
+ }
+
+ void return_void() noexcept {}
+
+ void unhandled_exception() {
+ if (__root_ != this) {
+ __exception_.get() = std::current_exception();
+ } else {
+ throw;
+ }
+ }
+
+ // Transfers control back to the parent of a nested coroutine
+ struct __final_awaiter {
+ bool await_ready() noexcept {
+ return false;
+ }
+
+ template <typename _Promise>
+ std::coroutine_handle<>
+ await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
+ _Promise& __promise = __h.promise();
+ __generator_promise_base& __root = *__promise.__root_;
+ if (&__root != &__promise) {
+ auto __parent = __promise.__parentOrLeaf_;
+ __root.__parentOrLeaf_ = __parent;
+ return __parent;
+ }
+ return std::noop_coroutine();
+ }
+
+ void await_resume() noexcept {}
+ };
+
+ __final_awaiter final_suspend() noexcept {
+ return {};
+ }
+
+ std::suspend_always yield_value(_Ref&& __x)
+ noexcept(std::is_nothrow_move_constructible_v<_Ref>) {
+ __root_->__value_.construct((_Ref&&)__x);
+ return {};
+ }
+
+ template <typename _T>
+ requires
+ (!std::is_reference_v<_Ref>) &&
+ std::is_convertible_v<_T, _Ref>
+ std::suspend_always yield_value(_T&& __x)
+ noexcept(std::is_nothrow_constructible_v<_Ref, _T>) {
+ __root_->__value_.construct((_T&&)__x);
+ return {};
+ }
+
+ template <typename _Gen>
+ struct __yield_sequence_awaiter {
+ _Gen __gen_;
+
+ __yield_sequence_awaiter(_Gen&& __g) noexcept
+ // Taking ownership of the generator ensures frame are destroyed
+ // in the reverse order of their execution.
+ : __gen_((_Gen&&)__g) {
+ }
+
+ bool await_ready() noexcept {
+ return false;
+ }
+
+ // set the parent, root and exceptions pointer and
+ // resume the nested
+ template<typename _Promise>
+ std::coroutine_handle<>
+ await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
+ __generator_promise_base& __current = __h.promise();
+ __generator_promise_base& __nested = *__gen_.__get_promise();
+ __generator_promise_base& __root = *__current.__root_;
+
+ __nested.__root_ = __current.__root_;
+ __nested.__parentOrLeaf_ = __h;
+
+ // Lazily construct the __exception_ member here now that we
+ // know it will be used as a nested generator. This will be
+ // destroyed by the promise destructor.
+ __nested.__exception_.construct();
+ __root.__parentOrLeaf_ = __gen_.__get_coro();
+
+ // Immediately resume the nested coroutine (nested generator)
+ return __gen_.__get_coro();
+ }
+
+ void await_resume() {
+ __generator_promise_base& __nestedPromise = *__gen_.__get_promise();
+ if (__nestedPromise.__exception_.get()) {
+ std::rethrow_exception(std::move(__nestedPromise.__exception_.get()));
+ }
+ }
+ };
+
+ template <typename _OValue, typename _OAlloc>
+ __yield_sequence_awaiter<generator<_Ref, _OValue, _OAlloc>>
+ yield_value(nihil::elements_of<generator<_Ref, _OValue, _OAlloc>> __g) noexcept {
+ return std::move(__g).get();
+ }
+
+ template <std::ranges::range _Rng, typename _Allocator>
+ __yield_sequence_awaiter<generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator>>
+ yield_value(nihil::elements_of<_Rng, _Allocator> && __x) {
+ return [](std::allocator_arg_t, _Allocator, auto && __rng) -> generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator> {
+ for(auto && e: __rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::allocator_arg, __x.get_allocator(), std::forward<_Rng>(__x.get()));
+ }
+
+ void resume() {
+ __parentOrLeaf_.resume();
+ }
+
+ // Disable use of co_await within this coroutine.
+ void await_transform() = delete;
+};
+
+template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator = false>
+struct __generator_promise;
+
+template<typename _Ref, typename _Value, typename _Alloc, typename _ByteAllocator, bool _ExplicitAllocator>
+struct __generator_promise<generator<_Ref, _Value, _Alloc>, _ByteAllocator, _ExplicitAllocator> final
+ : public __generator_promise_base<_Ref>
+ , public promise_base_alloc<_ByteAllocator> {
+ __generator_promise() noexcept
+ : __generator_promise_base<_Ref>(std::coroutine_handle<__generator_promise>::from_promise(*this))
+ {}
+
+ generator<_Ref, _Value, _Alloc> get_return_object() noexcept {
+ return generator<_Ref, _Value, _Alloc>{
+ std::coroutine_handle<__generator_promise>::from_promise(*this)
+ };
+ }
+
+ using __generator_promise_base<_Ref>::yield_value;
+
+ template <std::ranges::range _Rng>
+ typename __generator_promise_base<_Ref>::template __yield_sequence_awaiter<generator<_Ref, _Value, _Alloc>>
+ yield_value(nihil::elements_of<_Rng> && __x) {
+ static_assert (!_ExplicitAllocator,
+ "This coroutine has an explicit allocator specified with std::allocator_arg so an allocator needs to be passed "
+ "explicitely to std::elements_of");
+ return [](auto && __rng) -> generator<_Ref, _Value, _Alloc> {
+ for(auto && e: __rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::forward<_Rng>(__x.get()));
+ }
+};
+
+template<typename _Alloc>
+using __byte_allocator_t = typename std::allocator_traits<std::remove_cvref_t<_Alloc>>::template rebind_alloc<std::byte>;
+
+} // namespace nihil
+
+namespace std {
+
+// Type-erased allocator with default allocator behaviour.
+export template<typename _Ref, typename _Value, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, _Args...> {
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, std::allocator<std::byte>>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter
+export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter (non-static member functions)
+export template<typename _Ref, typename _Value, typename _This, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, _This, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
+};
+
+// Generator with specified allocator type
+export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value, _Alloc>, _Args...> {
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value, _Alloc>, __byte_allocator>;
+};
+
+} // namespace std
+
+namespace nihil {
+
+// TODO : make layout compatible promise casts possible
+export template <typename _Ref, typename _Value, typename _Alloc>
+class generator {
+ using __byte_allocator = __byte_allocator_t<_Alloc>;
+public:
+ using promise_type = __generator_promise<generator<_Ref, _Value, _Alloc>, __byte_allocator>;
+ friend promise_type;
+private:
+ using __coroutine_handle = std::coroutine_handle<promise_type>;
+public:
+
+ generator() noexcept = default;
+
+ generator(generator&& __other) noexcept
+ : __coro_(std::exchange(__other.__coro_, {}))
+ , __started_(std::exchange(__other.__started_, false)) {
+ }
+
+ ~generator() noexcept {
+ if (__coro_) {
+ if (__started_ && !__coro_.done()) {
+ __coro_.promise().__value_.destruct();
+ }
+ __coro_.destroy();
+ }
+ }
+
+ generator& operator=(generator && g) noexcept {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator& __other) noexcept {
+ std::swap(__coro_, __other.__coro_);
+ std::swap(__started_, __other.__started_);
+ }
+
+ struct sentinel {};
+
+ class iterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = _Value;
+ using reference = _Ref;
+ using pointer = std::add_pointer_t<_Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator&& __other) noexcept
+ : __coro_(std::exchange(__other.__coro_, {})) {
+ }
+
+ iterator& operator=(iterator&& __other) {
+ std::swap(__coro_, __other.__coro_);
+ return *this;
+ }
+
+ ~iterator() {
+ }
+
+ friend bool operator==(const iterator &it, sentinel) noexcept {
+ return it.__coro_.done();
+ }
+
+ iterator &operator++() {
+ __coro_.promise().__value_.destruct();
+ __coro_.promise().resume();
+ return *this;
+ }
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast<reference>(__coro_.promise().__value_.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(__coroutine_handle __coro) noexcept
+ : __coro_(__coro) {}
+
+ __coroutine_handle __coro_;
+ };
+
+ iterator begin() {
+ assert(__coro_);
+ assert(!__started_);
+ __started_ = true;
+ __coro_.resume();
+ return iterator{__coro_};
+ }
+
+ sentinel end() noexcept {
+ return {};
+ }
+
+private:
+ explicit generator(__coroutine_handle __coro) noexcept
+ : __coro_(__coro) {
+ }
+
+public: // to get around access restrictions for __yield_sequence_awaitable
+ std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
+ promise_type* __get_promise() noexcept { return std::addressof(__coro_.promise()); }
+
+private:
+ __coroutine_handle __coro_;
+ bool __started_ = false;
+};
+
+// Specialisation for type-erased allocator implementation.
+export template <typename _Ref, typename _Value>
+class generator<_Ref, _Value, use_allocator_arg> {
+ using __promise_base = __generator_promise_base<_Ref>;
+public:
+
+ generator() noexcept
+ : __promise_(nullptr)
+ , __coro_()
+ , __started_(false)
+ {}
+
+ generator(generator&& __other) noexcept
+ : __promise_(std::exchange(__other.__promise_, nullptr))
+ , __coro_(std::exchange(__other.__coro_, {}))
+ , __started_(std::exchange(__other.__started_, false)) {
+ }
+
+ ~generator() noexcept {
+ if (__coro_) {
+ if (__started_ && !__coro_.done()) {
+ __promise_->__value_.destruct();
+ }
+ __coro_.destroy();
+ }
+ }
+
+ generator& operator=(generator g) noexcept {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator& __other) noexcept {
+ std::swap(__promise_, __other.__promise_);
+ std::swap(__coro_, __other.__coro_);
+ std::swap(__started_, __other.__started_);
+ }
+
+ struct sentinel {};
+
+ class iterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = _Value;
+ using reference = _Ref;
+ using pointer = std::add_pointer_t<_Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator&& __other) noexcept
+ : __promise_(std::exchange(__other.__promise_, nullptr))
+ , __coro_(std::exchange(__other.__coro_, {}))
+ {}
+
+ iterator& operator=(iterator&& __other) {
+ __promise_ = std::exchange(__other.__promise_, nullptr);
+ __coro_ = std::exchange(__other.__coro_, {});
+ return *this;
+ }
+
+ ~iterator() = default;
+
+ friend bool operator==(const iterator &it, sentinel) noexcept {
+ return it.__coro_.done();
+ }
+
+ iterator& operator++() {
+ __promise_->__value_.destruct();
+ __promise_->resume();
+ return *this;
+ }
+
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast<reference>(__promise_->__value_.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(__promise_base* __promise, std::coroutine_handle<> __coro) noexcept
+ : __promise_(__promise)
+ , __coro_(__coro)
+ {}
+
+ __promise_base* __promise_;
+ std::coroutine_handle<> __coro_;
+ };
+
+ iterator begin() {
+ assert(__coro_);
+ assert(!__started_);
+ __started_ = true;
+ __coro_.resume();
+ return iterator{__promise_, __coro_};
+ }
+
+ sentinel end() noexcept {
+ return {};
+ }
+
+private:
+ template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator>
+ friend struct __generator_promise;
+
+ template<typename _Promise>
+ explicit generator(std::coroutine_handle<_Promise> __coro) noexcept
+ : __promise_(std::addressof(__coro.promise()))
+ , __coro_(__coro)
+ {}
+
+public: // to get around access restrictions for __yield_sequence_awaitable
+ std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
+ __promise_base* __get_promise() noexcept { return __promise_; }
+
+private:
+ __promise_base* __promise_;
+ std::coroutine_handle<> __coro_;
+ bool __started_ = false;
+};
+
+} // namespace nihil
diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm
new file mode 100644
index 0000000..d249e99
--- /dev/null
+++ b/nihil.generator/manual_lifetime.ccm
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <concepts>
+#include <memory>
+
+export module nihil.generator:manual_lifetime;
+
+namespace nihil {
+
+template <typename T>
+struct manual_lifetime {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ template <typename ...Args>
+ auto construct(this manual_lifetime &self, Args && ...args)
+ noexcept(std::is_nothrow_constructible_v<T, Args...>)
+ -> T &
+ {
+ return *::new (static_cast<void*>(std::addressof(self.m_value)))
+ T(static_cast<Args &&>(args)...);
+ }
+
+ void destruct(this manual_lifetime &self)
+ noexcept(std::is_nothrow_destructible_v<T>)
+ {
+ self.m_value.~T();
+ }
+
+ auto get(this manual_lifetime &self) noexcept -> T &
+ {
+ return self.m_value;
+ }
+
+ auto get(this manual_lifetime &&self) noexcept -> T &&
+ {
+ return static_cast<T&&>(self.m_value);
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T const &
+ {
+ return self.m_value;
+ }
+
+ auto get(this manual_lifetime const &&self) noexcept -> T const &&
+ {
+ return static_cast<T const &&>(self.m_value);
+ }
+
+private:
+ union {
+ std::remove_const_t<T> m_value;
+ };
+};
+
+template <typename T>
+class manual_lifetime<T &> {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ auto construct(this manual_lifetime &self, T &value) noexcept -> T &
+ {
+ self.m_value = std::addressof(value);
+ return self.m_value;
+ }
+
+ auto destruct(this manual_lifetime &) noexcept -> void
+ {
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T &
+ {
+ return *self.m_value;
+ }
+
+private:
+ T *m_value = nullptr;
+};
+
+template <typename T>
+class manual_lifetime<T &&> {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ auto construct(this manual_lifetime &self, T &&value) noexcept -> T &&
+ {
+ self.m_value = std::addressof(value);
+ return static_cast<T &&>(value);
+ }
+
+ void destruct(this manual_lifetime &) noexcept
+ {
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T &&
+ {
+ return static_cast<T &&>(*self.m_value);
+ }
+
+private:
+ T* m_value = nullptr;
+};
+
+}
diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm
new file mode 100644
index 0000000..9eec5b4
--- /dev/null
+++ b/nihil.generator/nihil.generator.ccm
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <ranges>
+
+export module nihil.generator;
+
+export import :elements_of;
+export import :generator;
+export import :manual_lifetime;
+export import :promise_base_alloc;
+export import :util;
+
+export namespace std::ranges {
+
+template <typename T, typename U, typename Alloc>
+constexpr inline bool enable_view<nihil::generator<T, U, Alloc>> = true;
+
+} // namespace std::ranges
+
diff --git a/nihil.generator/promise_base_alloc.ccm b/nihil.generator/promise_base_alloc.ccm
new file mode 100644
index 0000000..e59fc57
--- /dev/null
+++ b/nihil.generator/promise_base_alloc.ccm
@@ -0,0 +1,94 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (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 :util;
+
+namespace nihil {
+
+template<typename Alloc>
+struct promise_base_alloc
+{
+ template<typename... Args>
+ static void* operator new(std::size_t frame_size, std::allocator_arg_t, Alloc alloc, Args &...)
+ {
+ void* frame = alloc.allocate(padded_frame_size(frame_size));
+
+ // Store allocator at end of the coroutine frame. Assuming the
+ // allocator's move constructor is non-throwing (a requirement
+ // for allocators)
+ auto *alloc_address = static_cast<void*>(std::addressof(get_allocator(frame, frame_size)));
+ ::new (alloc_address) Alloc(std::move(alloc));
+
+ return frame;
+ }
+
+ template<typename This, typename... Args>
+ static void* operator new(std::size_t frame_size, This &, std::allocator_arg_t, Alloc alloc, Args &...)
+ {
+ return promise_base_alloc::operator new(frame_size, std::allocator_arg, std::move(alloc));
+ }
+
+ static void operator delete(void* ptr, std::size_t frame_size) noexcept
+ {
+ auto &alloc = get_allocator(ptr, frame_size);
+ auto local_alloc = Alloc(std::move(alloc));
+
+ alloc.~Alloc();
+
+ local_alloc.deallocate(static_cast<std::byte*>(ptr), padded_frame_size(frame_size));
+ }
+
+private:
+ [[nodiscard]] static constexpr auto offset_of_allocator(std::size_t frame_size) noexcept -> std::size_t
+ {
+ return aligned_allocation_size(frame_size, alignof(Alloc));
+ }
+
+ [[nodiscard]] static constexpr auto padded_frame_size(std::size_t frame_size) noexcept -> std::size_t
+ {
+ return offset_of_allocator(frame_size) + sizeof(Alloc);
+ }
+
+ [[nodiscard]] static auto get_allocator(void* frame, std::size_t frame_size) noexcept -> Alloc &
+ {
+ return *reinterpret_cast<Alloc*>(
+ static_cast<char*>(frame) + offset_of_allocator(frame_size));
+ }
+
+};
+
+template<typename Alloc>
+requires (!allocator_needs_to_be_stored<Alloc>)
+struct promise_base_alloc<Alloc>
+{
+ static void* operator new(std::size_t size)
+ {
+ auto alloc = Alloc();
+ return alloc.allocate(size);
+ }
+
+ static void operator delete(void *ptr, std::size_t size) noexcept
+ {
+ auto alloc = Alloc();
+ alloc.deallocate(static_cast<std::byte *>(ptr), size);
+ }
+};
+
+} // namespace nihil
diff --git a/nihil.generator/test.cc b/nihil.generator/test.cc
new file mode 100644
index 0000000..49272b4
--- /dev/null
+++ b/nihil.generator/test.cc
@@ -0,0 +1,56 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <coroutine>
+#include <ranges>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.generator;
+
+TEST_CASE("generator: basic", "[generator]")
+{
+ auto fn = [] () -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ co_yield 3;
+ };
+
+ auto values = std::vector<int>();
+ std::ranges::copy(fn(), std::back_inserter(values));
+
+ REQUIRE(values == std::vector{1, 2, 3});
+}
+
+TEST_CASE("generator: exceptions", "[generator]")
+{
+ auto fn = [] () -> nihil::generator<int> {
+ co_yield 1;
+ throw std::runtime_error("test");
+ };
+
+ auto range = fn();
+ auto it = std::ranges::begin(range);
+ REQUIRE(*it == 1);
+ REQUIRE_THROWS_AS(it++, std::runtime_error);
+}
+
+TEST_CASE("generator: elements_of", "[generator]")
+{
+ auto fn1 = [] -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ co_yield 3;
+ };
+
+ auto fn2 = [&fn1] -> nihil::generator<int> {
+ co_yield nihil::elements_of(fn1());
+ };
+
+ auto values = std::vector<int>();
+ std::ranges::copy(fn2(), std::back_inserter(values));
+
+ REQUIRE(values == std::vector{1, 2, 3});
+}
diff --git a/nihil.generator/util.ccm b/nihil.generator/util.ccm
new file mode 100644
index 0000000..4d732b9
--- /dev/null
+++ b/nihil.generator/util.ccm
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <concepts>
+#include <memory>
+
+export module nihil.generator:util;
+
+namespace nihil {
+
+export struct use_allocator_arg {};
+
+template <typename Alloc>
+constexpr bool allocator_needs_to_be_stored =
+ !std::allocator_traits<Alloc>::is_always_equal::value ||
+ !std::is_default_constructible_v<Alloc>;
+
+// Round s up to next multiple of a.
+[[nodiscard]] constexpr auto
+aligned_allocation_size(std::size_t s, std::size_t a) -> std::size_t
+{
+ return (s + a - 1) & ~(a - 1);
+}
+
+} // namespace nihil
diff --git a/nihil.guard/CMakeLists.txt b/nihil.guard/CMakeLists.txt
new file mode 100644
index 0000000..bba4284
--- /dev/null
+++ b/nihil.guard/CMakeLists.txt
@@ -0,0 +1,23 @@
+# This source code is released into the public domain.
+
+add_library(nihil.guard STATIC)
+target_sources(nihil.guard
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ guard.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.guard.test test.cc)
+ target_link_libraries(nihil.guard.test PRIVATE
+ nihil.guard
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.guard.test)
+endif()
diff --git a/nihil.guard/guard.ccm b/nihil.guard/guard.ccm
new file mode 100644
index 0000000..7b6cf66
--- /dev/null
+++ b/nihil.guard/guard.ccm
@@ -0,0 +1,53 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <concepts>
+#include <functional>
+#include <optional>
+#include <utility>
+
+export module nihil.guard;
+
+namespace nihil {
+
+/*
+ * guard: invoke a callable when this object is destroyed; this is similar to
+ * scope_exit from the library fundamentals TS, which LLVM doesn't implement.
+ */
+export template<std::invocable F>
+struct guard final {
+ // Initialise the guard with a callable we will invoke later.
+ guard(F func) : m_func(std::move(func)) {}
+
+ /*
+ * We are being destroyed, so call the callable.
+ * If the callable throws, std::terminate() will be called.
+ */
+ ~guard()
+ {
+ if (m_func)
+ std::invoke(*m_func);
+ }
+
+ // Release the guard. This turns the destructor into a no-op.
+ auto release(this guard &self) noexcept -> void
+ {
+ self.m_func.reset();
+ }
+
+ // Not default-constructible, movable or copyable.
+ guard() = delete;
+ guard(guard const &) = delete;
+ guard(guard &&) noexcept = delete;
+ auto operator=(this guard &, guard const &) -> guard & = delete;
+ auto operator=(this guard &, guard &&) noexcept -> guard & = delete;
+
+private:
+ // The callable to be invoked when we are destroyed.
+ std::optional<F> m_func;
+};
+
+} // namespace nihil
diff --git a/nihil.guard/test.cc b/nihil.guard/test.cc
new file mode 100644
index 0000000..11f7d37
--- /dev/null
+++ b/nihil.guard/test.cc
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+ {
+ auto guard = nihil::guard([&] { n = 1; });
+ REQUIRE(n == 0);
+ }
+
+ REQUIRE(n == 1);
+}
diff --git a/nihil.match/CMakeLists.txt b/nihil.match/CMakeLists.txt
new file mode 100644
index 0000000..d7c5875
--- /dev/null
+++ b/nihil.match/CMakeLists.txt
@@ -0,0 +1,23 @@
+# This source code is released into the public domain.
+
+add_library(nihil.match STATIC)
+target_sources(nihil.match
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ match.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.match.test test.cc)
+ target_link_libraries(nihil.match.test PRIVATE
+ nihil.match
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.match.test)
+endif()
diff --git a/nihil.match/match.ccm b/nihil.match/match.ccm
new file mode 100644
index 0000000..d67bd0b
--- /dev/null
+++ b/nihil.match/match.ccm
@@ -0,0 +1,23 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <variant>
+
+export module nihil.match;
+
+namespace nihil {
+
+export template<class... Ts>
+struct match : Ts... { using Ts::operator()...; };
+
+export template<typename... Ts, typename... Fs>
+[[nodiscard]] constexpr decltype(auto) operator|
+ (std::variant<Ts...> const &v, match<Fs...> const &match)
+{
+ return std::visit(match, v);
+}
+
+} // namespace nihil
diff --git a/nihil.match/test.cc b/nihil.match/test.cc
new file mode 100644
index 0000000..7dd1c34
--- /dev/null
+++ b/nihil.match/test.cc
@@ -0,0 +1,34 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <string>
+#include <variant>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.match;
+
+TEST_CASE("match", "[nihil]")
+{
+ using namespace nihil;
+ using namespace std::literals;
+
+ auto v = std::variant<int, std::string>(42);
+
+ auto s = v | match {
+ [](int) { return "int"s; },
+ [](std::string const &) { return "string"s; }
+ };
+
+ REQUIRE(s == "int");
+
+ v = "test"s;
+
+ s = v | match {
+ [](int) { return "int"s; },
+ [](std::string const &) { return "string"s; }
+ };
+
+ REQUIRE(s == "string");
+}
diff --git a/nihil.monad/CMakeLists.txt b/nihil.monad/CMakeLists.txt
new file mode 100644
index 0000000..b0fa095
--- /dev/null
+++ b/nihil.monad/CMakeLists.txt
@@ -0,0 +1,24 @@
+# This source code is released into the public domain.
+
+add_library(nihil.monad STATIC)
+target_link_libraries(nihil.monad PRIVATE nihil.error)
+target_sources(nihil.monad
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ monad.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.monad.test test.cc)
+ target_link_libraries(nihil.monad.test PRIVATE
+ nihil.monad
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.monad.test)
+endif()
diff --git a/nihil.monad/monad.ccm b/nihil.monad/monad.ccm
new file mode 100644
index 0000000..b462e4c
--- /dev/null
+++ b/nihil.monad/monad.ccm
@@ -0,0 +1,289 @@
+/*
+ * From https://github.com/toby-allsopp/coroutine_monad
+ *
+ * Copyright (c) 2017 Toby Allsopp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+module;
+
+#include <coroutine>
+#include <exception>
+#include <expected>
+#include <optional>
+#include <utility>
+
+export module nihil.monad;
+
+namespace nihil {
+
+/**********************************************************************
+ * return_object_holder
+ */
+
+// An object that starts out unitialized. Initialized by a call to emplace.
+template <typename T>
+using deferred = std::optional<T>;
+
+template <typename T>
+struct return_object_holder {
+ // The staging object that is returned (by copy/move) to the caller of
+ // the coroutine.
+ deferred<T> stage;
+ return_object_holder*& p;
+
+ // When constructed, we assign a pointer to ourselves to the supplied
+ // reference to pointer.
+ return_object_holder(return_object_holder*& p)
+ : stage{}
+ , p(p)
+ {
+ p = this;
+ }
+
+ // Copying doesn't make any sense (which copy should the pointer refer
+ // to?).
+ return_object_holder(return_object_holder const&) = delete;
+
+ // To move, we just update the pointer to point at the new object.
+ return_object_holder(return_object_holder&& other)
+ : stage(std::move(other.stage))
+ , p(other.p)
+ {
+ p = this;
+ }
+
+ // Assignment doesn't make sense.
+ void operator=(return_object_holder const&) = delete;
+ void operator=(return_object_holder&&) = delete;
+
+ // A non-trivial destructor is required until
+ // https://bugs.llvm.org//show_bug.cgi?id=28593 is fixed.
+ ~return_object_holder() {}
+
+ // Construct the staging value; arguments are perfect forwarded to T's
+ // constructor.
+ template <typename... Args>
+ void emplace(Args&&... args)
+ {
+ stage.emplace(std::forward<Args>(args)...);
+ }
+
+ // We assume that we will be converted only once, so we can move from
+ // the staging object. We also assume that `emplace` has been called
+ // at least once.
+ operator T()
+ {
+ return std::move(*stage);
+ }
+};
+
+template <typename T>
+auto make_return_object_holder(return_object_holder<T>*& p)
+{
+ return return_object_holder<T>{p};
+}
+
+/**********************************************************************
+ * std::optional
+ */
+
+template <typename T>
+struct optional_promise {
+ return_object_holder<std::optional<T>>* data;
+
+ auto get_return_object()
+ {
+ return make_return_object_holder(data);
+ }
+
+ auto initial_suspend() noexcept -> std::suspend_never
+ {
+ return {};
+ }
+
+ auto final_suspend() noexcept -> std::suspend_never
+ {
+ return {};
+ }
+
+ void return_value(T x)
+ {
+ data->emplace(std::move(x));
+ }
+
+ void unhandled_exception()
+ {
+ std::rethrow_exception(std::current_exception());
+ }
+};
+
+} // namespace nihil
+
+export template <typename T, typename... Args>
+struct std::coroutine_traits<std::optional<T>, Args...> {
+ using promise_type = nihil::optional_promise<T>;
+};
+
+namespace nihil {
+
+template <typename T>
+struct optional_awaitable {
+ std::optional<T> o;
+
+ auto await_ready()
+ {
+ return o.has_value();
+ }
+
+ auto await_resume()
+ {
+ return *o;
+ }
+
+ template <typename U>
+ void await_suspend(std::coroutine_handle<optional_promise<U>> h)
+ {
+ h.promise().data->emplace(std::nullopt);
+ h.destroy();
+ }
+};
+
+} // namespace nihil
+
+namespace std {
+
+export template <typename T>
+auto operator co_await(std::optional<T> o) {
+ return nihil::optional_awaitable<T>{std::move(o)};
+}
+
+} // namespace std
+
+/**********************************************************************
+ * std::expected
+ */
+
+namespace nihil {
+
+export template <typename T, typename E>
+struct expected_promise_base {
+ return_object_holder<std::expected<T, E>>* data;
+
+ auto get_return_object()
+ {
+ return make_return_object_holder(data);
+ }
+
+ auto initial_suspend() noexcept -> std::suspend_never
+ {
+ return {};
+ }
+
+ auto final_suspend() noexcept -> std::suspend_never
+ {
+ return {};
+ }
+
+ void unhandled_exception()
+ {
+ std::rethrow_exception(std::current_exception());
+ }
+};
+
+export template <typename T, typename E>
+struct expected_promise : expected_promise_base<T, E> {
+ void return_value(this expected_promise &self, std::unexpected<E> err)
+ {
+ self.data->emplace(std::move(err));
+ }
+
+ void return_value(this expected_promise &self, T o)
+ {
+ self.data->emplace(std::move(o));
+ }
+};
+
+export template <typename E>
+struct expected_promise<void, E> : expected_promise_base<void, E> {
+ void return_value(this expected_promise &self, std::unexpected<E> err)
+ {
+ self.data->emplace(std::move(err));
+ }
+
+ void return_value(this expected_promise &self,
+ std::expected<void, E> o)
+ {
+ self.data->emplace(std::move(o));
+ }
+};
+
+} // namespace nihil
+
+export template <typename T, typename E, typename... Args>
+struct std::coroutine_traits<std::expected<T, E>, Args...> {
+ using promise_type = nihil::expected_promise<T, E>;
+};
+
+namespace nihil {
+
+export template<typename T, typename E>
+struct expected_awaitable_base {
+ std::expected<T, E> o;
+
+ auto await_ready()
+ {
+ return o.has_value();
+ }
+
+ template <typename P>
+ void await_suspend(std::coroutine_handle<P> h)
+ {
+ h.promise().data->emplace(std::unexpected(o.error()));
+ h.destroy();
+ }
+};
+
+export template <typename T, typename E>
+struct expected_awaitable : expected_awaitable_base<T, E> {
+ auto await_resume(this expected_awaitable &self)
+ {
+ return std::move(*self.o);
+ }
+};
+
+export template <typename E>
+struct expected_awaitable<void, E> : expected_awaitable_base<void, E> {
+ auto await_resume(this expected_awaitable &)
+ {
+ return std::expected<void, E>();
+ }
+};
+
+} // namespace nihil
+
+namespace std {
+
+export template <typename T, typename E>
+auto operator co_await(std::expected<T, E> o) {
+ return nihil::expected_awaitable<T, E>{std::move(o)};
+}
+
+} // namespace std
diff --git a/nihil.monad/test.cc b/nihil.monad/test.cc
new file mode 100644
index 0000000..347acdb
--- /dev/null
+++ b/nihil.monad/test.cc
@@ -0,0 +1,69 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <coroutine>
+#include <expected>
+#include <optional>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.error;
+import nihil.monad;
+
+TEST_CASE("monad: co_await std::optional<> with value", "[nihil]")
+{
+ auto get_value = [] -> std::optional<int> {
+ return 42;
+ };
+
+ auto try_get_value = [&get_value] -> std::optional<int> {
+ co_return co_await get_value();
+ };
+
+ auto o = try_get_value();
+ REQUIRE(o == 42);
+}
+
+TEST_CASE("monad: co_await std::optional<> without value", "[nihil]")
+{
+ auto get_value = [] -> std::optional<int> {
+ return {};
+ };
+
+ auto try_get_value = [&get_value] -> std::optional<int> {
+ co_return co_await get_value();
+ };
+
+ auto o = try_get_value();
+ REQUIRE(!o.has_value());
+}
+
+TEST_CASE("monad: co_await std::expected<> with value", "[nihil]")
+{
+ auto get_value = [] -> std::expected<int, std::string> {
+ return 42;
+ };
+
+ auto try_get_value = [&get_value] -> std::expected<int, std::string> {
+ co_return co_await get_value();
+ };
+
+ auto o = try_get_value();
+ REQUIRE(o == 42);
+}
+
+TEST_CASE("monad: co_await std::expected<> with error", "[nihil]")
+{
+ auto get_value = [] -> std::expected<int, std::string> {
+ return std::unexpected("error");
+ };
+
+ auto try_get_value = [&get_value] -> std::expected<int, std::string> {
+ co_return co_await get_value();
+ };
+
+ auto o = try_get_value();
+ REQUIRE(!o);
+ REQUIRE(o.error() == "error");
+}
diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt
new file mode 100644
index 0000000..62f6aaf
--- /dev/null
+++ b/nihil.posix/CMakeLists.txt
@@ -0,0 +1,61 @@
+# This source code is released into the public domain.
+
+add_library(nihil.posix STATIC)
+target_link_libraries(nihil.posix PRIVATE
+ nihil.core nihil.error nihil.flagset nihil.guard nihil.monad)
+
+target_sources(nihil.posix
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ posix.ccm
+
+ argv.ccm
+ ensure_dir.ccm
+ exec.ccm
+ execv.ccm
+ executor.ccm
+ fd.ccm
+ find_in_path.ccm
+ getenv.ccm
+ open.ccm
+ process.ccm
+ read_file.ccm
+ rename.ccm
+ spawn.ccm
+ tempfile.ccm
+ write_file.ccm
+
+ PRIVATE
+ argv.cc
+ ensure_dir.cc
+ exec.cc
+ execv.cc
+ getenv.cc
+ fd.cc
+ find_in_path.cc
+ open.cc
+ process.cc
+ rename.cc
+ tempfile.cc
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.posix.test
+ test.fd.cc
+ test.getenv.cc
+ test.spawn.cc
+ test.tempfile.cc
+ )
+
+ target_link_libraries(nihil.posix.test PRIVATE
+ nihil.posix
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.posix.test)
+endif()
diff --git a/nihil.posix/argv.cc b/nihil.posix/argv.cc
new file mode 100644
index 0000000..e6b1389
--- /dev/null
+++ b/nihil.posix/argv.cc
@@ -0,0 +1,65 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <memory>
+#include <ranges>
+#include <string>
+#include <vector>
+
+module nihil.posix;
+
+namespace nihil {
+
+argv::argv() = default;
+argv::argv(argv &&) noexcept = default;
+auto argv::operator=(this argv &, argv &&) -> argv & = default;
+
+argv::~argv()
+{
+ for (auto *arg : m_args)
+ delete[] arg;
+}
+
+auto argv::data(this argv const &self) -> char const * const *
+{
+ return self.m_args.data();
+}
+
+auto argv::data(this argv &self) -> char * const *
+{
+ return self.m_args.data();
+}
+
+auto argv::size(this argv const &self)
+{
+ return self.m_args.size();
+}
+
+auto argv::begin(this argv const &self)
+{
+ return self.m_args.begin();
+}
+
+auto argv::end(this argv const &self)
+{
+ return self.m_args.end();
+}
+
+
+auto argv::add_arg(this argv &self, std::string_view arg) -> void
+{
+ // Create a nul-terminated C string.
+ auto ptr = std::make_unique<char[]>(arg.size() + 1);
+ std::ranges::copy(arg, ptr.get());
+ ptr[arg.size()] = '\0';
+
+ // Ensure we won't throw when emplacing the pointer.
+ self.m_args.reserve(self.m_args.size() + 1);
+ self.m_args.emplace_back(ptr.release());
+}
+
+} // namespace nihil
+
diff --git a/nihil.posix/argv.ccm b/nihil.posix/argv.ccm
new file mode 100644
index 0000000..6f60f4b
--- /dev/null
+++ b/nihil.posix/argv.ccm
@@ -0,0 +1,78 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <memory>
+#include <ranges>
+#include <string>
+#include <vector>
+
+export module nihil.posix:argv;
+
+namespace nihil {
+
+/*
+ * argv: stores a null-terminated array of nul-terminated C strings.
+ * argv::data() is suitable for passing to ::execv().
+ *
+ * Create an argv using argv::from_range(), which takes a range of
+ * string-like objects.
+ */
+
+export struct argv {
+ /*
+ * Create a new argv from a range.
+ */
+ argv(std::from_range_t, std::ranges::range auto &&args)
+ {
+ for (auto &&arg : args)
+ add_arg(std::string_view(arg));
+
+ m_args.push_back(nullptr);
+ }
+
+ /*
+ * Create an argv from an initializer list.
+ */
+ template<typename T>
+ explicit argv(std::initializer_list<T> &&args)
+ : argv(std::from_range, std::forward<decltype(args)>(args))
+ {
+ }
+
+ // Movable.
+ argv(argv &&) noexcept;
+ auto operator=(this argv &, argv &&other) -> argv &;
+
+ // Not copyable. TODO: for completeness, it probably should be.
+ argv(argv const &) = delete;
+ auto operator=(this argv &, argv const &other) -> argv& = delete;
+
+ ~argv();
+
+ // Access the stored arguments.
+ [[nodiscard]] auto data(this argv const &self) -> char const * const *;
+ [[nodiscard]] auto data(this argv &self) -> char * const *;
+ [[nodiscard]] auto size(this argv const &self);
+
+ // Range access
+ [[nodiscard]] auto begin(this argv const &self);
+ [[nodiscard]] auto end(this argv const &self);
+
+private:
+ // Use the from_range() factory method to create new instances.
+ argv();
+
+ // The argument pointers, including the null terminator.
+ // This can't be a vector<unique_ptr> because we need an array of
+ // char pointers to pass to exec.
+ std::vector<char *> m_args;
+
+ // Add a new argument to the array.
+ auto add_arg(this argv &self, std::string_view arg) -> void;
+};
+
+} // namespace nihil
+
diff --git a/nihil.posix/ensure_dir.cc b/nihil.posix/ensure_dir.cc
new file mode 100644
index 0000000..88e8898
--- /dev/null
+++ b/nihil.posix/ensure_dir.cc
@@ -0,0 +1,30 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+#include <format>
+#include <system_error>
+
+module nihil.posix;
+
+import nihil.error;
+
+namespace nihil {
+
+auto ensure_dir(std::filesystem::path const &dir) -> std::expected<void, error>
+{
+ auto err = std::error_code();
+
+ std::filesystem::create_directories(dir, err);
+
+ if (err)
+ return std::unexpected(error(err));
+
+ return {};
+}
+
+} // namespace nihil
diff --git a/nihil.posix/ensure_dir.ccm b/nihil.posix/ensure_dir.ccm
new file mode 100644
index 0000000..fa92a90
--- /dev/null
+++ b/nihil.posix/ensure_dir.ccm
@@ -0,0 +1,23 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+export module nihil.posix:ensure_dir;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * Create the given directory and any parent directories.
+ */
+export [[nodiscard]] auto ensure_dir(std::filesystem::path const &dir)
+ -> std::expected<void, error>;
+
+} // namespace nihil
+
diff --git a/nihil.posix/exec.cc b/nihil.posix/exec.cc
new file mode 100644
index 0000000..5bdcbf7
--- /dev/null
+++ b/nihil.posix/exec.cc
@@ -0,0 +1,71 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <coroutine>
+#include <expected>
+#include <format>
+#include <string>
+#include <utility>
+
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+extern char **environ;
+
+module nihil.posix;
+
+import nihil.error;
+import nihil.monad;
+
+namespace nihil {
+
+fexecv::fexecv(fd &&execfd, argv &&args) noexcept
+ : m_execfd(std::move(execfd))
+ , m_args(std::move(args))
+{
+}
+
+auto fexecv::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))));
+}
+
+fexecv::fexecv(fexecv &&) noexcept = default;
+auto fexecv::operator=(this fexecv &, fexecv &&) noexcept -> fexecv& = default;
+
+auto execv(std::string_view path, argv &&argv)
+ -> std::expected<fexecv, error>
+{
+ auto file = co_await open(path, O_EXEC)
+ .transform_error([&] (error cause) {
+ return error(std::format("could not open {}", path),
+ std::move(cause));
+ });
+
+ co_return fexecv(std::move(file), std::move(argv));
+}
+
+auto execvp(std::string_view file, argv &&argv)
+ -> std::expected<fexecv, error>
+{
+ auto execfd = find_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));
+}
+
+auto shell(std::string_view const &command)
+ -> std::expected<fexecv, error>
+{
+ return execl("/bin/sh", "sh", "-c", command);
+}
+
+} // namespace nihil
diff --git a/nihil.posix/exec.ccm b/nihil.posix/exec.ccm
new file mode 100644
index 0000000..6098318
--- /dev/null
+++ b/nihil.posix/exec.ccm
@@ -0,0 +1,105 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * Exec providers, mostly used for spawn().
+ */
+
+#include <expected>
+#include <string>
+
+export module nihil.posix:exec;
+
+import nihil.error;
+import :argv;
+import :fd;
+
+namespace nihil {
+
+/*
+ * A concept to mark spawn executors.
+ */
+
+export struct exec_tag{};
+
+export template<typename T>
+concept executor =
+ requires (T e) {
+ std::same_as<exec_tag, typename std::remove_cvref_t<T>::tag>;
+ { e.exec() };
+ };
+
+/*
+ * 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.
+ *
+ * TODO: Should have a way to pass the environment (envp).
+ */
+export struct fexecv final {
+ using tag = exec_tag;
+
+ fexecv(fd &&execfd, argv &&args) noexcept;
+
+ [[nodiscard]] auto exec(this fexecv &self)
+ -> std::expected<void, error>;
+
+ // Movable
+ fexecv(fexecv &&) noexcept;
+ auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv&;
+
+ // 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;
+};
+
+/*
+ * execv: equivalent to fexecv(), except the command is passed as
+ * a pathname instead of a file descriptor. Does not search $PATH.
+ */
+export [[nodiscard]] auto execv(std::string_view path, argv &&argv)
+ -> std::expected<fexecv, error>;
+
+/*
+ * execvp: equivalent to fexecv(), 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 execvp(std::string_view file, argv &&argv)
+ -> std::expected<fexecv, error>;
+
+/*
+ * 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)
+ -> std::expected<fexecv, error>
+{
+ return execv(path, argv({std::string_view(args)...}));
+}
+
+/*
+ * 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<fexecv, error>
+{
+ return execvp(file, argv({std::string_view(args)...}));
+}
+
+/*
+ * shell: run the process by invoking /bin/sh -c with the single argument,
+ * equivalent to system(3).
+ */
+export [[nodiscard]] auto shell(std::string_view const &command)
+ -> std::expected<fexecv, error>;
+
+} // namespace nihil
diff --git a/nihil.posix/executor.ccm b/nihil.posix/executor.ccm
new file mode 100644
index 0000000..f348dc8
--- /dev/null
+++ b/nihil.posix/executor.ccm
@@ -0,0 +1,27 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <concepts>
+#include <type_traits>
+
+export module nihil.posix:executor;
+
+namespace nihil {
+
+/*
+ * A concept to mark spawn executors.
+ */
+
+export struct exec_tag{};
+
+export template<typename T>
+concept executor =
+ requires (T e) {
+ std::same_as<exec_tag, typename std::remove_cvref_t<T>::tag>;
+ { e.exec() };
+ };
+
+} // namespace nihil
diff --git a/nihil.posix/execv.cc b/nihil.posix/execv.cc
new file mode 100644
index 0000000..63f9698
--- /dev/null
+++ b/nihil.posix/execv.cc
@@ -0,0 +1,43 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <coroutine>
+#include <expected>
+#include <format>
+#include <string>
+#include <utility>
+
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+extern char **environ;
+
+module nihil.posix;
+
+import nihil.error;
+import nihil.monad;
+
+namespace nihil {
+
+execv::execv(std::filesystem::path path, argv &&args) noexcept
+ : m_path(std::move(path))
+ , m_args(std::move(args))
+{
+}
+
+auto execv::exec(this execv &self) -> std::expected<void, error>
+{
+ ::execve(self.m_path.string().c_str(), self.m_args.data(), environ);
+ return std::unexpected(error("execve failed", error(std::errc(errno))));
+}
+
+execv::execv(execv &&) noexcept = default;
+execv::execv(execv const &) = default;
+auto execv::operator=(this execv &, execv &&) -> execv & = default;
+auto execv::operator=(this execv &, execv const &) -> execv & = default;
+
+} // namespace nihil
diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm
new file mode 100644
index 0000000..ca036a1
--- /dev/null
+++ b/nihil.posix/execv.ccm
@@ -0,0 +1,47 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+#include <string>
+
+export module nihil.posix:execv;
+
+import nihil.error;
+import :argv;
+import :executor;
+
+namespace nihil {
+
+/*
+ * execv: use a filename and an argument vector to call ::execve().
+ * This is the lowest-level executor which all others are implemented
+ * in terms of, if fexecve is not available.
+ *
+ * TODO: Should have a way to pass the environment (envp).
+ */
+export struct execv final
+{
+ using tag = exec_tag;
+
+ execv(std::filesystem::path, argv &&) noexcept;
+
+ [[nodiscard]] auto exec(this execv &) -> std::expected<void, error>;
+
+ // Movable
+ execv(execv &&) noexcept;
+ auto operator=(this execv &, execv &&) noexcept -> execv &;
+
+ // Copyable.
+ execv(execv const &);
+ auto operator=(this execv &, execv const &) -> execv &;
+
+private:
+ std::filesystem::path m_path;
+ argv m_args;
+};
+
+} // namespace nihil
diff --git a/nihil.posix/fd.cc b/nihil.posix/fd.cc
new file mode 100644
index 0000000..6d5e54f
--- /dev/null
+++ b/nihil.posix/fd.cc
@@ -0,0 +1,220 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <coroutine>
+#include <expected>
+#include <format>
+#include <stdexcept>
+#include <system_error>
+
+module nihil.posix;
+
+import nihil.error;
+import nihil.monad;
+
+namespace nihil {
+
+fd::fd() noexcept = default;
+
+fd::fd(int fileno) noexcept
+ : m_fileno(fileno)
+{
+}
+
+fd::~fd()
+{
+ if (*this)
+ std::ignore = this->close();
+}
+
+fd::fd(fd &&other) noexcept
+ : m_fileno(std::exchange(other.m_fileno, invalid_fileno))
+{
+}
+
+auto fd::operator=(this fd &self, fd &&other) noexcept -> fd &
+{
+ if (&self != &other)
+ self.m_fileno = std::exchange(other.m_fileno, invalid_fileno);
+ return self;
+}
+
+fd::operator bool(this fd const &self) noexcept
+{
+ return self.m_fileno != invalid_fileno;
+}
+
+auto fd::close(this fd &self) -> std::expected<void, error>
+{
+ auto const ret = ::close(self.get());
+ self.m_fileno = invalid_fileno;
+
+ if (ret == 0)
+ return {};
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto fd::get(this fd const &self) -> int
+{
+ if (self)
+ return self.m_fileno;
+ throw std::logic_error("Attempt to call get() on invalid fd");
+}
+
+auto fd::release(this fd &&self) -> int
+{
+ if (self)
+ return std::exchange(self.m_fileno, invalid_fileno);
+ throw std::logic_error("Attempt to release an invalid fd");
+}
+
+auto dup(fd const &self) -> std::expected<fd, error>
+{
+ auto const newfd = ::dup(self.get());
+ if (newfd != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto dup(fd const &self, int newfd) -> std::expected<fd, error>
+{
+ auto const ret = ::dup2(self.get(), newfd);
+ if (ret != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto raw_dup(fd const &self) -> std::expected<int, error>
+{
+ auto const newfd = ::dup(self.get());
+ if (newfd != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto raw_dup(fd const &self, int newfd) -> std::expected<int, error>
+{
+ auto const ret = ::dup2(self.get(), newfd);
+ if (ret != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto getflags(fd const &self) -> std::expected<int, error>
+{
+ auto const flags = ::fcntl(self.get(), F_GETFL);
+ if (flags != -1)
+ return flags;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto replaceflags(fd &self, int newflags) -> std::expected<void, error>
+{
+ auto const ret = ::fcntl(self.get(), F_SETFL, newflags);
+ if (ret == 0)
+ return {};
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto setflags(fd &self, int newflags) -> std::expected<int, error>
+{
+ auto flags = co_await getflags(self);
+
+ flags |= newflags;
+ co_await replaceflags(self, flags);
+
+ co_return flags;
+}
+
+auto clearflags(fd &self, int clrflags) -> std::expected<int, error>
+{
+ auto flags = co_await getflags(self);
+
+ flags &= ~clrflags;
+ co_await replaceflags(self, flags);
+
+ co_return flags;
+}
+
+auto getfdflags(fd const &self) -> std::expected<int, error>
+{
+ auto const flags = ::fcntl(self.get(), F_GETFD);
+ if (flags != -1)
+ return flags;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto replacefdflags(fd &self, int newflags) -> std::expected<void, error>
+{
+ auto const ret = ::fcntl(self.get(), F_SETFD, newflags);
+ if (ret != -1)
+ return {};
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto setfdflags(fd &self, int newflags) -> std::expected<int, error>
+{
+ auto flags = co_await getfdflags(self);
+
+ flags |= newflags;
+ co_await replacefdflags(self, flags);
+
+ co_return flags;
+}
+
+auto clearfdflags(fd &self, int clrflags) -> std::expected<int, error>
+{
+ auto flags = co_await getfdflags(self);
+
+ flags &= ~clrflags;
+ co_await replacefdflags(self, flags);
+
+ co_return flags;
+}
+
+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 {{fd(fds[0]), fd(fds[1])}};
+}
+
+auto fd::write(this fd &self, std::span<std::byte const> buffer)
+ -> std::expected<std::size_t, error>
+{
+ auto const ret = ::write(self.get(), buffer.data(), buffer.size());
+ if (ret >= 0)
+ return ret;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+auto fd::read(this fd &self, std::span<std::byte> buffer)
+ -> std::expected<std::span<std::byte>, error>
+{
+ auto const ret = ::read(self.get(), buffer.data(), buffer.size());
+ if (ret >= 0)
+ return buffer.subspan(0, ret);
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+} // namespace nihil
diff --git a/nihil.posix/fd.ccm b/nihil.posix/fd.ccm
new file mode 100644
index 0000000..b937f46
--- /dev/null
+++ b/nihil.posix/fd.ccm
@@ -0,0 +1,157 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <coroutine>
+#include <expected>
+#include <ranges>
+#include <span>
+#include <stdexcept>
+#include <system_error>
+
+export module nihil.posix:fd;
+
+import nihil.error;
+import nihil.monad;
+
+namespace nihil {
+
+/*
+ * fd: a file descriptor.
+ */
+
+export struct fd final {
+ // Construct an empty (invalid) fd.
+ fd() noexcept;
+
+ // Construct an fd from an exising file destrictor, taking ownership.
+ fd(int fd_) noexcept;
+
+ // Destructor. Close the fd, discarding any errors.
+ ~fd();
+
+ // Move from another fd, leaving the moved-from fd in an invalid state.
+ fd(fd &&other) noexcept;
+ auto operator=(this fd &, fd &&other) noexcept -> fd &;
+
+ // Not copyable.
+ fd(fd const &) = delete;
+ fd& operator=(this fd &, fd const &) = delete;
+
+ // Return true if this fd is valid (open).
+ [[nodiscard]] explicit operator bool(this fd const &self) noexcept;
+
+ // Close the wrapped fd.
+ [[nodiscard]] auto close(this fd &self) -> std::expected<void, error>;
+
+ // Return the stored fd.
+ [[nodiscard]] auto get(this fd const &self) -> int;
+
+ // Release the stored fd and return it. The caller must close it.
+ [[nodiscard]] auto release(this fd &&self) -> int;
+
+ // Write data from the provided buffer to the fd. Returns the
+ // number of bytes written.
+ [[nodiscard]] auto write(this fd &self, std::span<std::byte const>)
+ -> std::expected<std::size_t, error>;
+
+ // Read data from the fd to the provided buffer. Returns a
+ // subspan containing the data which was read.
+ [[nodiscard]] auto read(this fd &self, std::span<std::byte>)
+ -> std::expected<std::span<std::byte>, error>;
+
+private:
+ static constexpr int invalid_fileno = -1;
+
+ int m_fileno = invalid_fileno;
+};
+
+// Create a copy of this fd by calling dup().
+export [[nodiscard]] auto dup(fd const &self) -> std::expected<fd, error>;
+
+// Create a copy of this fd by calling dup2(). Note that because this results
+// in the existing fd and the new fd both being managed by an fd instance,
+// there are two potential cases that can cause problems:
+//
+// - dup()ing an fd to itself (a no-op)
+// - dup()ing an fd to an fd which is already managed by an fd instance
+//
+// In both of these cases, either use raw_dup() instead, or immediately call
+// release() on the returned fd to prevent the fd instance from closing it.
+export [[nodiscard]] auto dup(fd const &self, int newfd)
+ -> std::expected<fd, error>;
+
+// Create a copy of this fd by calling dup().
+export [[nodiscard]] auto raw_dup(fd const &self)
+ -> std::expected<int, error>;
+
+// Create a copy of this fd by calling dup2().
+export [[nodiscard]] auto raw_dup(fd const &self, int newfd)
+ -> std::expected<int, error>;
+
+// Return the fnctl flags for this fd.
+export [[nodiscard]] auto getflags(fd const &self)
+ -> std::expected<int, error>;
+
+// Replace the fnctl flags for this fd.
+export [[nodiscard]] auto replaceflags(fd &self, int newflags)
+ -> std::expected<void, error>;
+
+// Add bits to the fcntl flags for this fd. Returns the new flags.
+export [[nodiscard]] auto setflags(fd &self, int newflags)
+ -> std::expected<int, error>;
+
+// Remove bits from the fcntl flags for this fd. Returns the new flags.
+export [[nodiscard]] auto clearflags(fd &self, int clrflags)
+ -> std::expected<int, error>;
+
+// Return the fd flags for this fd.
+export [[nodiscard]] auto getfdflags(fd const &self)
+ -> std::expected<int, error>;
+
+// Replace the fd flags for this fd.
+export [[nodiscard]] auto replacefdflags(fd &self, int newflags)
+ -> std::expected<void, error>;
+
+// Add bits to the fd flags for this fd. Returns the new flags.
+export [[nodiscard]] auto setfdflags(fd &self, int newflags)
+ -> std::expected<int, error>;
+
+// Remove bits from the fd flags for this fd. Returns the new flags.
+export [[nodiscard]] auto clearfdflags(fd &self, int clrflags)
+ -> std::expected<int, error>;
+
+// Create two fds by calling pipe() and return them.
+export [[nodiscard]] auto pipe() -> std::expected<std::pair<fd, fd>, error>;
+
+/*
+ * Write data to a file descriptor from the provided range. Returns the
+ * number of bytes written.
+ */
+export [[nodiscard]] auto write(fd &file,
+ std::ranges::contiguous_range auto &&range)
+ -> std::expected<std::size_t, error>
+requires(sizeof(std::ranges::range_value_t<decltype(range)>) == 1)
+{
+ return file.write(as_bytes(std::span(range)));
+}
+
+/*
+ * Read data from a file descriptor into the provided buffer. Returns a
+ * span containing the data that was read.
+ */
+export [[nodiscard]] auto read(fd &file,
+ std::ranges::contiguous_range auto &&range)
+ -> std::expected<
+ std::span<std::ranges::range_value_t<decltype(range)>>,
+ error>
+requires(sizeof(std::ranges::range_value_t<decltype(range)>) == 1)
+{
+ auto bspan = as_writable_bytes(std::span(range));
+ auto rspan = co_await file.read(bspan);
+ co_return std::span(range).subspan(0, rspan.size());
+}
+
+} // namespace nihil
diff --git a/nihil.posix/fexecv.ccm b/nihil.posix/fexecv.ccm
new file mode 100644
index 0000000..5ad6c62
--- /dev/null
+++ b/nihil.posix/fexecv.ccm
@@ -0,0 +1,53 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <string>
+
+#include "nihil.hh"
+
+#ifdef NIHIL_HAVE_FEXECVE
+
+export module nihil.posix:fexecv;
+
+import nihil.error;
+import :argv;
+import :executor;
+import :fd;
+
+namespace nihil {
+
+/*
+ * 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;
+
+ [[nodiscard]] auto exec(this fexecv &self)
+ -> std::expected<void, error>;
+
+ // Movable
+ fexecv(fexecv &&) noexcept;
+ auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv&;
+
+ // 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;
+};
+
+} // namespace nihil
+
+#endif // NIHIL_HAVE_FEXECVE \ No newline at end of file
diff --git a/nihil.posix/find_in_path.cc b/nihil.posix/find_in_path.cc
new file mode 100644
index 0000000..7b03faa
--- /dev/null
+++ b/nihil.posix/find_in_path.cc
@@ -0,0 +1,52 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <filesystem>
+#include <optional>
+#include <ranges>
+#include <string>
+
+#include <fcntl.h>
+#include <paths.h>
+#include <unistd.h>
+
+module nihil.posix;
+
+namespace nihil {
+
+auto find_in_path(std::filesystem::path const &file) -> std::optional<std::filesystem::path>
+{
+ using namespace std::literals;
+
+ auto try_return = [](std::filesystem::path file)
+ -> std::optional<std::filesystem::path>
+ {
+ auto ret = ::access(file.string().c_str(), X_OK);
+ if (ret == 0)
+ return {std::move(file)};
+ return {};
+ };
+
+ // Absolute pathname skips the search.
+ if (file.is_absolute())
+ return try_return(file);
+
+ auto path = getenv("PATH").value_or(_PATH_DEFPATH);
+
+ for (auto &&dir : path | std::views::split(':')) {
+ // An empty $PATH element means cwd.
+ auto sdir = dir.empty()
+ ? std::filesystem::path(".")
+ : std::filesystem::path(std::string_view(dir));
+
+ if (auto ret = try_return(sdir / file); ret)
+ return ret;
+ }
+
+ return {};
+}
+
+} // namespace nihil
diff --git a/nihil.posix/find_in_path.ccm b/nihil.posix/find_in_path.ccm
new file mode 100644
index 0000000..4988a12
--- /dev/null
+++ b/nihil.posix/find_in_path.ccm
@@ -0,0 +1,24 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <filesystem>
+#include <optional>
+
+export module nihil.posix:find_in_path;
+
+import nihil.error;
+import :fd;
+
+namespace nihil {
+
+/*
+ * Find an executable in $PATH and return the full path. If $PATH is not set, uses _PATH_DEFPATH.
+ * If the file can't be found or opened, returns std::nullopt.
+ */
+export [[nodiscard]] auto find_in_path(std::filesystem::path const &file)
+ -> std::optional<std::filesystem::path>;
+
+} // namespace nihil
diff --git a/nihil.posix/getenv.cc b/nihil.posix/getenv.cc
new file mode 100644
index 0000000..ad93305
--- /dev/null
+++ b/nihil.posix/getenv.cc
@@ -0,0 +1,54 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cstdint>
+#include <expected>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <unistd.h>
+
+#include "nihil.hh"
+
+module nihil.posix;
+
+import nihil.error;
+
+namespace nihil {
+
+auto getenv(std::string_view varname) -> std::expected<std::string, error>
+{
+ auto cvarname = std::string(varname);
+
+#ifdef NIHIL_HAVE_GETENV_R
+ // Start with a buffer of this size, and double it every iteration.
+ constexpr auto bufinc = std::size_t{1024};
+
+ auto buf = std::vector<char>(bufinc);
+ for (;;) {
+ auto const ret = ::getenv_r(cvarname.c_str(),
+ buf.data(), buf.size());
+
+ if (ret == 0)
+ return {std::string(buf.data())};
+
+ if (ret == -1 && errno == ERANGE) {
+ buf.resize(buf.size() * 2);
+ continue;
+ }
+
+ return std::unexpected(error(std::errc(errno)));
+ }
+#else // NIHIL_HAVE_GETENV_R
+ auto *v = ::getenv(cvarname.c_str());
+ if (v == nullptr)
+ return std::unexpected(error(std::errc(errno)));
+ return {std::string(v)};
+#endif // NIHIL_HAVE_GETENV_R
+}
+
+} // namespace nihil
diff --git a/nihil.posix/getenv.ccm b/nihil.posix/getenv.ccm
new file mode 100644
index 0000000..465f7e7
--- /dev/null
+++ b/nihil.posix/getenv.ccm
@@ -0,0 +1,23 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <string>
+
+export module nihil.posix:getenv;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * Find a variable by the given name in the environment by calling getenv_r().
+ */
+
+export [[nodiscard]] auto getenv(std::string_view varname)
+ -> std::expected<std::string, error>;
+
+} // namespace nihil
diff --git a/nihil.posix/open.cc b/nihil.posix/open.cc
new file mode 100644
index 0000000..9ef6538
--- /dev/null
+++ b/nihil.posix/open.cc
@@ -0,0 +1,31 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+#include <system_error>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+module nihil.posix;
+
+import nihil.error;
+import :fd;
+
+namespace nihil {
+
+auto open(std::filesystem::path const &filename, int flags, int mode)
+ -> std::expected<fd, error>
+{
+ auto fdno = ::open(filename.c_str(), flags, mode);
+ if (fdno != -1)
+ return fd(fdno);
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+} // namespace nihil
diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm
new file mode 100644
index 0000000..eaedacd
--- /dev/null
+++ b/nihil.posix/open.ccm
@@ -0,0 +1,24 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+export module nihil.posix:open;
+
+import nihil.error;
+import :fd;
+
+export namespace nihil {
+
+/*
+ * Open the given file and return an fd for it.
+ */
+[[nodiscard]] auto open(std::filesystem::path const &filename,
+ int flags, int mode = 0777)
+ -> std::expected<fd, error>;
+
+} // namespace nihil
diff --git a/nihil.posix/open_in_path.cc b/nihil.posix/open_in_path.cc
new file mode 100644
index 0000000..30021ca
--- /dev/null
+++ b/nihil.posix/open_in_path.cc
@@ -0,0 +1,51 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <filesystem>
+#include <optional>
+#include <ranges>
+#include <string>
+
+#include <fcntl.h>
+#include <paths.h>
+
+module nihil.posix;
+
+namespace nihil {
+
+auto open_in_path(std::filesystem::path const &file) -> std::optional<fd>
+{
+ using namespace std::literals;
+
+ auto try_open =
+ [](std::filesystem::path const &file) -> std::optional<fd>
+ {
+ auto ret = open(file, O_EXEC);
+ if (ret)
+ return {std::move(*ret)};
+ return {};
+ };
+
+ // Absolute pathname skips the search.
+ if (file.is_absolute())
+ return try_open(file);
+
+ auto path = getenv("PATH").value_or(_PATH_DEFPATH);
+
+ for (auto &&dir : path | std::views::split(':')) {
+ // An empty $PATH element means cwd.
+ auto sdir = dir.empty()
+ ? std::filesystem::path(".")
+ : std::filesystem::path(std::string_view(dir));
+
+ if (auto ret = try_open(sdir / file); ret)
+ return ret;
+ }
+
+ return {};
+}
+
+} // namespace nihil
diff --git a/nihil.posix/open_in_path.ccm b/nihil.posix/open_in_path.ccm
new file mode 100644
index 0000000..1fae56b
--- /dev/null
+++ b/nihil.posix/open_in_path.ccm
@@ -0,0 +1,23 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <filesystem>
+#include <optional>
+
+export module nihil.posix:find_in_path;
+
+import nihil.error;
+import :fd;
+
+namespace nihil {
+
+/*
+ * Find an executable in $PATH and open it with O_EXEC. If $PATH is not set, uses _PATH_DEFPATH.
+ * If the file can't be found or opened, returns std::nullopt.
+ */
+export [[nodiscard]] auto open_in_path(std::filesystem::path const &file) -> std::optional<fd>;
+
+} // namespace nihil
diff --git a/nihil.posix/posix.ccm b/nihil.posix/posix.ccm
new file mode 100644
index 0000000..ea13f81
--- /dev/null
+++ b/nihil.posix/posix.ccm
@@ -0,0 +1,35 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+#include <optional>
+#include <string>
+
+#include "nihil.hh"
+
+export module nihil.posix;
+
+import nihil.error;
+
+export import :argv;
+export import :ensure_dir;
+export import :exec;
+export import :execv;
+export import :fd;
+export import :find_in_path;
+export import :getenv;
+export import :open;
+export import :process;
+export import :read_file;
+export import :rename;
+export import :spawn;
+export import :tempfile;
+export import :write_file;
+
+#ifdef NIHIL_HAVE_FEXECVE
+export import :fexecv;
+#endif
diff --git a/nihil.posix/process.cc b/nihil.posix/process.cc
new file mode 100644
index 0000000..70e84b7
--- /dev/null
+++ b/nihil.posix/process.cc
@@ -0,0 +1,102 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cerrno>
+#include <cstring>
+#include <expected>
+#include <format>
+#include <optional>
+#include <system_error>
+#include <utility>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+module nihil.posix;
+
+import nihil.error;
+
+namespace nihil {
+
+auto wait_result::okay(this wait_result const &self) -> bool
+{
+ return self.status() == 0;
+}
+
+wait_result::operator bool(this wait_result const &self)
+{
+ return self.okay();
+}
+
+auto wait_result::status(this wait_result const &self) -> std::optional<int>
+{
+ if (WIFEXITED(self._status))
+ return WEXITSTATUS(self._status);
+ return {};
+}
+
+auto wait_result::signal(this wait_result const &self) -> std::optional<int>
+{
+ if (WIFSIGNALED(self._status))
+ return WTERMSIG(self._status);
+ return {};
+}
+
+wait_result::wait_result(int status)
+ : _status(status)
+{}
+
+process::process(::pid_t pid)
+ : m_pid(pid)
+{}
+
+process::~process() {
+ if (m_pid == -1)
+ return;
+
+ auto status = int{};
+ std::ignore = waitpid(m_pid, &status, WEXITED);
+}
+
+process::process(process &&other) noexcept
+ : m_pid(std::exchange(other.m_pid, -1))
+{
+}
+
+auto process::operator=(this process &self, process &&other) noexcept
+ -> process &
+{
+ if (&self != &other) {
+ self.m_pid = std::exchange(other.m_pid, -1);
+ }
+
+ return self;
+}
+
+// Get the child's process id.
+auto process::pid(this process const &self) noexcept -> ::pid_t
+{
+ return self.m_pid;
+}
+
+auto process::wait(this process &&self) -> std::expected<wait_result, error>
+{
+ auto status = int{};
+ auto ret = waitpid(self.m_pid, &status, WEXITED);
+ if (ret == -1)
+ return std::unexpected(error(std::errc(errno)));
+
+ return wait_result(status);
+}
+
+auto process::release(this process &&self) -> ::pid_t
+{
+ auto const ret = self.pid();
+ self.m_pid = -1;
+ return ret;
+}
+
+} // namespace nihil
diff --git a/nihil.posix/process.ccm b/nihil.posix/process.ccm
new file mode 100644
index 0000000..425deac
--- /dev/null
+++ b/nihil.posix/process.ccm
@@ -0,0 +1,91 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <optional>
+#include <system_error>
+#include <utility>
+
+#include <sys/types.h>
+
+export module nihil.posix:process;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * wait_result: the exit status of a process.
+ */
+export struct wait_result final {
+ // Return true if the process exited normally with an exit code of
+ // zero, otherwise false.
+ [[nodiscard]] auto okay(this wait_result const &self) -> bool;
+ [[nodiscard]] explicit operator bool(this wait_result const &self);
+
+ // Return the exit status, if any.
+ [[nodiscard]] auto status(this wait_result const &self)
+ -> std::optional<int>;
+
+ // Return the exit signal, if any.
+ [[nodiscard]] auto signal(this wait_result const &self)
+ -> std::optional<int>;
+
+private:
+ friend struct process;
+
+ int _status;
+
+ // Construct a new wait_result from the output of waitpid().
+ wait_result(int status);
+};
+
+/*
+ * process: represents a process we created, which can be waited for.
+ */
+export struct process final {
+ process() = delete;
+
+ /*
+ * Create a new process from a pid, which must be a child of the
+ * current process.
+ */
+ process(::pid_t pid);
+
+ // When destroyed, we automatically wait for the process to
+ // avoid creating zombie processes.
+ ~process();
+
+ // Movable.
+ process(process &&) noexcept;
+ auto operator=(this process &, process &&) noexcept -> process &;
+
+ // Not copyable.
+ process(process const &) = delete;
+ auto operator=(this process &, process const &) -> process & = delete;
+
+ // Get the child's process id.
+ [[nodiscard]] auto pid(this process const &self) noexcept -> ::pid_t;
+
+ /*
+ * Wait for this process to exit (by calling waitpid()) and return
+ * its exit status. This destroys the process state, leaving this
+ * object in a moved-from state.
+ */
+ [[nodiscard]] auto wait(this process &&self)
+ -> std::expected<wait_result, error>;
+
+ /*
+ * Release this process so we won't try to wait for it when
+ * destroying this object.
+ */
+ [[nodiscard]] auto release(this process &&self) -> ::pid_t;
+
+private:
+ ::pid_t m_pid;
+};
+
+} // namespace nihil
diff --git a/nihil.posix/read_file.ccm b/nihil.posix/read_file.ccm
new file mode 100644
index 0000000..be9e102
--- /dev/null
+++ b/nihil.posix/read_file.ccm
@@ -0,0 +1,49 @@
+/*
+ * 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>
+
+export module nihil.posix:read_file;
+
+import nihil.error;
+import nihil.monad;
+import :fd;
+import :open;
+
+namespace nihil {
+
+/*
+ * 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)
+ -> std::expected<void, error>
+{
+ auto file = co_await open(filename, O_RDONLY);
+
+ auto constexpr bufsize = std::size_t{1024};
+ auto buffer = std::array<char, bufsize>{};
+
+ for (;;) {
+ auto read_buf = co_await(read(file, buffer));
+ if (read_buf.empty())
+ co_return {};
+
+ std::ranges::copy(read_buf, iter);
+ }
+}
+
+} // namespace nihil
diff --git a/nihil.posix/rename.cc b/nihil.posix/rename.cc
new file mode 100644
index 0000000..9203d08
--- /dev/null
+++ b/nihil.posix/rename.cc
@@ -0,0 +1,34 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+module nihil.posix;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * Rename a file.
+ */
+auto rename_file(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);
+
+ if (err)
+ return std::unexpected(error(err));
+
+ return {};
+}
+
+
+} // namespace nihil
diff --git a/nihil.posix/rename.ccm b/nihil.posix/rename.ccm
new file mode 100644
index 0000000..796ec5b
--- /dev/null
+++ b/nihil.posix/rename.ccm
@@ -0,0 +1,23 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <filesystem>
+
+export module nihil.posix:rename;
+
+import nihil.error;
+
+namespace nihil {
+
+/*
+ * Rename a file (or directory).
+ */
+export [[nodiscard]] auto
+rename(std::filesystem::path const &oldp, std::filesystem::path const &newp)
+ -> std::expected<void, error>;
+
+} // namespace nihil
diff --git a/nihil.posix/spawn.ccm b/nihil.posix/spawn.ccm
new file mode 100644
index 0000000..4cce334
--- /dev/null
+++ b/nihil.posix/spawn.ccm
@@ -0,0 +1,246 @@
+/*
+ * 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;
+
+import nihil.monad;
+import :argv;
+import :exec;
+import :open;
+import :process;
+
+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_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();
+ */
+export struct fd_pipe final {
+ fd_pipe(int fdno, fd &&child_fd, fd &&parent_fd)
+ : m_fdno(fdno)
+ , m_child_fd(std::move(child_fd))
+ , m_parent_fd(std::move(parent_fd))
+ {
+ }
+
+ auto run_in_child(this fd_pipe &self, process &) -> void
+ {
+ auto err = raw_dup(self.m_child_fd, self.m_fdno);
+ if (!err) {
+ std::print("dup: {}\n", err.error());
+ _exit(1);
+ }
+
+ /*
+ * We don't care about errors from close() since the fd
+ * is still closed.
+ */
+ std::ignore = self.m_parent_fd.close();
+ std::ignore = self.m_child_fd.close();
+ }
+
+ auto run_in_parent(this fd_pipe &self, process &) -> void
+ {
+ std::ignore = self.m_child_fd.close();
+ }
+
+ [[nodiscard]] auto parent_fd(this fd_pipe &self) -> fd &
+ {
+ return self.m_parent_fd;
+ }
+
+private:
+ int m_fdno;
+ fd m_child_fd;
+ fd m_parent_fd;
+};
+
+export [[nodiscard]] auto
+make_fd_pipe(int fdno) -> std::expected<fd_pipe, error>
+{
+ auto fds = co_await pipe();
+ co_return fd_pipe(fdno, std::move(fds.first), std::move(fds.second));
+}
+
+/*
+ * 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 {
+ fd_file(int fdno, fd &&file_fd)
+ : m_fdno(fdno)
+ , m_file_fd(std::move(file_fd))
+ {
+ }
+
+ auto run_in_parent(this fd_file &self, process &) -> void
+ {
+ std::ignore = self.m_file_fd.close();
+ }
+
+ auto run_in_child(this fd_file &self, process &) -> void
+ {
+ auto err = raw_dup(self.m_file_fd, self.m_fdno);
+ if (!err) {
+ std::print("dup: {}\n", err.error());
+ _exit(1);
+ }
+
+ std::ignore = self.m_file_fd.close();
+ }
+
+private:
+ int m_fdno;
+ fd m_file_fd;
+};
+
+export [[nodiscard]] auto
+make_fd_file(int fdno, std::filesystem::path const &file,
+ int flags, int mode = 0777)
+ -> std::expected<fd_file, error>
+{
+ auto fd = co_await open(file, flags, mode);
+ co_return fd_file(fdno, std::move(fd));
+}
+
+/*
+ * Shorthand for fd_file with /dev/null as the file.
+ */
+
+export [[nodiscard]] inline auto
+stdin_devnull() -> std::expected<fd_file, error>
+{
+ return make_fd_file(stdin_fileno, "/dev/null", O_RDONLY);
+}
+
+export [[nodiscard]] inline auto
+stdout_devnull() -> std::expected<fd_file, error>
+{
+ return make_fd_file(stdout_fileno, "/dev/null", O_WRONLY);
+}
+
+export [[nodiscard]] inline auto
+stderr_devnull() -> std::expected<fd_file, error>
+{
+ return make_fd_file(stderr_fileno, "/dev/null", O_WRONLY);
+}
+
+/*
+ * 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 {
+ fd_capture(fd_pipe &&pipe, Iterator it)
+ : m_pipe(std::move(pipe))
+ , m_iterator(std::move(it))
+ {
+ }
+
+ auto run_in_child(this fd_capture &self, process &p) -> void
+ {
+ self.m_pipe.run_in_child(p);
+ }
+
+ auto run_in_parent(this fd_capture &self, process &p) -> void
+ {
+ self.m_pipe.run_in_parent(p);
+
+ auto constexpr bufsize = std::size_t{1024};
+ auto buffer = std::array<char, bufsize>();
+ auto &fd = self.m_pipe.parent_fd();
+ for (;;) {
+ auto ret = read(fd, buffer);
+ if (!ret || ret->size() == 0)
+ break;
+
+ std::ranges::copy(*ret, self.m_iterator);
+ }
+
+ // We probably want to handle errors here somehow,
+ // but it's not clear what would be useful behaviour.
+ }
+
+private:
+ fd_pipe m_pipe;
+ Iterator m_iterator;
+};
+
+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_pipe(fdno);
+ co_return fd_capture(std::move(pipe),
+ std::forward<decltype(it)>(it));
+}
+
+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_pipe(fdno);
+ co_return fd_capture(std::move(pipe), std::back_inserter(str));
+}
+
+/*
+ * Spawn a new process with the given arguments and return a struct process.
+ * Throws exec_error() on failure.
+ */
+export [[nodiscard]] auto
+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 proc = process(pid);
+
+ if (pid == 0) {
+ // We are in the child.
+ (actions.run_in_child(proc), ...);
+ std::ignore = std::move(proc).release();
+
+ auto err = executor.exec();
+ std::print("{}\n", err.error());
+ _exit(1);
+ }
+
+ (actions.run_in_parent(proc), ...);
+
+ return proc;
+}
+
+} // namespace nihil
diff --git a/nihil.posix/tempfile.cc b/nihil.posix/tempfile.cc
new file mode 100644
index 0000000..b1d3dee
--- /dev/null
+++ b/nihil.posix/tempfile.cc
@@ -0,0 +1,128 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <algorithm>
+#include <coroutine>
+#include <expected>
+#include <filesystem>
+#include <random>
+#include <string>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+module nihil.posix;
+
+import nihil.flagset;
+import :getenv;
+import :open;
+
+namespace nihil {
+
+temporary_file::temporary_file(nihil::fd &&fd,
+ std::filesystem::path path) noexcept
+ : m_fd(std::move(fd))
+ , m_path(std::move(path))
+{
+}
+
+temporary_file::temporary_file(nihil::fd &&fd) noexcept
+ : m_fd(std::move(fd))
+{
+}
+
+temporary_file::temporary_file(temporary_file &&other) noexcept
+ : m_fd(std::move(other.m_fd))
+ , m_path(std::move(other.m_path))
+{
+}
+
+temporary_file::~temporary_file() //NOLINT(bugprone-exception-escape)
+{
+ if (m_fd)
+ release();
+}
+
+auto temporary_file::release(this temporary_file &self) -> void
+{
+ if (!self.m_fd)
+ 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);
+
+ self.m_path.clear();
+ }
+
+ std::ignore = self.m_fd.close();
+}
+
+auto temporary_file::path(this temporary_file const &self)
+ -> std::filesystem::path const &
+{
+ if (self.m_path.empty())
+ throw std::logic_error(
+ "path() called on unlinked temporary_file");
+
+ return self.m_path;
+}
+
+auto temporary_file::fd(this temporary_file &self) -> nihil::fd &
+{
+ if (!self.m_fd)
+ throw std::logic_error("fd() called on empty temporary_file");
+
+ return self.m_fd;
+}
+
+auto tempfile(tempfile_flags_t flags) -> std::expected<temporary_file, error>
+{
+ auto rng = std::default_random_engine(std::random_device{}());
+
+ auto random_name = [&] -> std::string {
+ auto constexpr length = std::size_t{12};
+ auto constexpr randchars = std::string_view(
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789");
+
+ auto dist = std::uniform_int_distribution<>(
+ 0, randchars.size() - 1);
+ auto ret = std::string(length, 0);
+ std::ranges::generate_n(ret.begin(), length,
+ [&] -> char {
+ return randchars[dist(rng)];
+ });
+ return ret;
+ };
+
+ auto dir = std::filesystem::path(getenv("TMPDIR").value_or("/tmp"));
+
+ // Keep trying until we don't get EEXIST.
+ for (;;) {
+ auto filename = dir / (random_name() + ".tmp");
+ auto fd = nihil::open(filename, O_RDWR | O_CREAT | O_EXCL,
+ 0600);
+ if (!fd) {
+ if (fd.error() == std::errc::file_exists)
+ continue;
+ return std::unexpected(fd.error());
+ }
+
+ if (flags & tempfile_unlink) {
+ auto ec = std::error_code();
+ remove(filename, ec);
+ return temporary_file(std::move(*fd));
+ } else {
+ return temporary_file(std::move(*fd),
+ std::move(filename));
+ }
+ }
+}
+
+} // namespace nihil
diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm
new file mode 100644
index 0000000..82f3be4
--- /dev/null
+++ b/nihil.posix/tempfile.ccm
@@ -0,0 +1,87 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * tempfile: create a temporary file.
+ */
+
+#include <cstdint>
+#include <expected>
+#include <filesystem>
+#include <string>
+
+export module nihil.posix:tempfile;
+
+import nihil.error;
+import nihil.flagset;
+import :fd;
+
+namespace nihil {
+
+struct tempfile_flags_tag {};
+export using tempfile_flags_t = flagset<std::uint8_t, tempfile_flags_tag>;
+
+// No flags.
+export inline constexpr auto tempfile_none = tempfile_flags_t();
+
+// Unlink the tempfile immediately after creating it
+export inline constexpr auto tempfile_unlink = tempfile_flags_t::bit<0>();
+
+export struct temporary_file final {
+ /*
+ * Fetch the file's fd.
+ */
+ [[nodiscard]] auto fd(this temporary_file &) -> nihil::fd &;
+
+ /*
+ * Fetch the name of this file. If tempfile_unlink was specified,
+ * throws std::logic_error.
+ */
+ [[nodiscard]] auto path(this temporary_file const &)
+ -> std::filesystem::path const &;
+
+ /*
+ * Release this temporary file, causing it to be deleted immediately.
+ * Throws std::logic_error if the file has already been released.
+ */
+ auto release(this temporary_file &) -> void;
+
+ /*
+ * Destructor; unlink the file if we didn't already.
+ */
+ ~temporary_file();
+
+ // Not copyable.
+ temporary_file(temporary_file const &) = delete;
+
+ // Movable.
+ temporary_file(temporary_file &&other) noexcept;
+
+ // Not assignable.
+ auto operator=(this temporary_file &, temporary_file const &)
+ -> temporary_file & = delete;
+ auto operator=(this temporary_file &, temporary_file &&) noexcept
+ -> temporary_file & = delete;
+
+private:
+ // The file descriptor for the file.
+ nihil::fd m_fd;
+ std::filesystem::path m_path;
+
+ temporary_file(nihil::fd &&fd, std::filesystem::path) noexcept;
+ temporary_file(nihil::fd &&fd) noexcept;
+
+ friend auto tempfile(tempfile_flags_t flags)
+ -> std::expected<temporary_file, error>;
+};
+
+/*
+ * Create a temporary file and return it.
+ */
+export [[nodiscard]] auto tempfile(tempfile_flags_t flags = tempfile_none)
+ -> std::expected<temporary_file, error>;
+
+} // namespace nihil
diff --git a/nihil.posix/test.fd.cc b/nihil.posix/test.fd.cc
new file mode 100644
index 0000000..6b6394b
--- /dev/null
+++ b/nihil.posix/test.fd.cc
@@ -0,0 +1,199 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <span>
+#include <stdexcept>
+
+#include <stdio.h>
+#include <fcntl.h>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.error;
+import nihil.posix;
+
+using namespace std::literals;
+
+namespace {
+
+// Test if an fd is open.
+auto fd_is_open(int fd) -> bool {
+ auto const ret = ::fcntl(fd, F_GETFL);
+ return ret == 0;
+}
+
+} // anonymous namespace
+
+TEST_CASE("fd: construct empty", "[fd]") {
+ nihil::fd fd;
+
+ REQUIRE(!fd);
+ REQUIRE_THROWS_AS(fd.get(), std::logic_error);
+}
+
+TEST_CASE("fd: construct from fd", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ {
+ auto fd = nihil::fd(file);
+ REQUIRE(fd_is_open(fd.get()));
+ }
+
+ REQUIRE(!fd_is_open(file));
+}
+
+TEST_CASE("fd: close", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd = nihil::fd(file);
+ REQUIRE(fd);
+
+ auto const ret = fd.close();
+ REQUIRE(ret);
+ REQUIRE(!fd_is_open(file));
+}
+
+TEST_CASE("fd: move construct", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd1 = nihil::fd(file);
+ REQUIRE(fd_is_open(fd1.get()));
+
+ auto fd2(std::move(fd1));
+ REQUIRE(!fd1); //NOLINT
+ REQUIRE(fd2);
+ REQUIRE(fd2.get() == file);
+ REQUIRE(fd_is_open(file));
+}
+
+TEST_CASE("fd: move assign", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd1 = nihil::fd(file);
+ REQUIRE(fd_is_open(fd1.get()));
+
+ auto fd2 = nihil::fd();
+ REQUIRE(!fd2);
+
+ fd2 = std::move(fd1);
+
+ REQUIRE(!fd1); //NOLINT
+ REQUIRE(fd2);
+ REQUIRE(fd2.get() == file);
+ REQUIRE(fd_is_open(file));
+}
+
+TEST_CASE("fd: release", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd = nihil::fd(file);
+ auto fdesc = std::move(fd).release();
+ REQUIRE(!fd); //NOLINT
+ REQUIRE(fdesc == file);
+}
+
+TEST_CASE("fd: dup", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd = nihil::fd(file);
+ REQUIRE(fd);
+
+ auto fd2 = dup(fd);
+ REQUIRE(fd2);
+ REQUIRE(fd.get() != fd2->get());
+}
+
+TEST_CASE("fd: dup2", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ REQUIRE(!fd_is_open(666));
+
+ auto fd = nihil::fd(file);
+ auto fd2 = dup(fd, 666);
+
+ REQUIRE(fd);
+ REQUIRE(fd2);
+ REQUIRE(fd2->get() == 666);
+}
+
+TEST_CASE("fd: flags", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd = nihil::fd(file);
+
+ {
+ auto const ret = replaceflags(fd, 0);
+ REQUIRE(ret);
+ REQUIRE(getflags(fd) == 0);
+ }
+
+ {
+ auto const ret = setflags(fd, O_NONBLOCK);
+ REQUIRE(ret == O_NONBLOCK);
+ REQUIRE(getflags(fd) == O_NONBLOCK);
+ }
+
+ {
+ auto const ret = setflags(fd, O_SYNC);
+ REQUIRE(ret == (O_NONBLOCK|O_SYNC));
+ REQUIRE(getflags(fd) == (O_NONBLOCK|O_SYNC));
+ }
+
+ {
+ auto const ret = clearflags(fd, O_NONBLOCK);
+ REQUIRE(ret == O_SYNC);
+ REQUIRE(getflags(fd) == O_SYNC);
+ }
+}
+
+TEST_CASE("fd: fdflags", "[fd]") {
+ auto file = ::open("/dev/null", O_RDONLY);
+ REQUIRE(file > 0);
+
+ auto fd = nihil::fd(file);
+
+ {
+ auto const ret = replacefdflags(fd, 0);
+ REQUIRE(ret);
+ REQUIRE(getfdflags(fd) == 0);
+ }
+
+ {
+ auto const ret = setfdflags(fd, FD_CLOEXEC);
+ REQUIRE(ret == FD_CLOEXEC);
+ REQUIRE(getfdflags(fd) == FD_CLOEXEC);
+ }
+
+ {
+ auto const ret = clearfdflags(fd, FD_CLOEXEC);
+ REQUIRE(ret == 0);
+ REQUIRE(getfdflags(fd) == 0);
+ }
+}
+
+TEST_CASE("fd: pipe, read, write", "[fd]") {
+ auto fds = nihil::pipe();
+ REQUIRE(fds);
+
+ auto [fd1, fd2] = std::move(*fds);
+
+ auto constexpr test_string = "test string"sv;
+
+ auto ret = write(fd1, test_string);
+ REQUIRE(ret);
+ REQUIRE(*ret == test_string.size());
+
+ auto readbuf = std::array<char, test_string.size() * 2>{};
+ auto read_buf = read(fd2, readbuf);
+ REQUIRE(read_buf);
+ REQUIRE(std::string_view(*read_buf) == test_string);
+}
diff --git a/nihil.posix/test.getenv.cc b/nihil.posix/test.getenv.cc
new file mode 100644
index 0000000..9e10c16
--- /dev/null
+++ b/nihil.posix/test.getenv.cc
@@ -0,0 +1,50 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <ranges>
+#include <string>
+#include <system_error>
+
+#include <unistd.h>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.error;
+import nihil.posix;
+
+TEST_CASE("getenv: existing value", "[getenv]")
+{
+ auto constexpr *name = "NIHIL_TEST_VAR";
+ auto constexpr *value = "test is a test";
+
+ REQUIRE(::setenv(name, value, 1) == 0);
+
+ auto const s = nihil::getenv(name);
+ REQUIRE(s);
+ REQUIRE(*s == value);
+}
+
+TEST_CASE("getenv: non-existing value", "[getenv]")
+{
+ auto constexpr *name = "NIHIL_TEST_VAR";
+
+ REQUIRE(::unsetenv(name) == 0);
+
+ auto const s = nihil::getenv(name);
+ REQUIRE(!s);
+ REQUIRE(s.error() == std::errc::no_such_file_or_directory);
+}
+
+// Force the call to getenv_r() to reallocate.
+TEST_CASE("getenv: long value")
+{
+ auto constexpr *name = "NIHIL_TEST_VAR";
+ auto const value = std::string(4096, 'a');
+
+ REQUIRE(::setenv(name, value.c_str(), 1) == 0);
+
+ auto const s = nihil::getenv(name);
+ REQUIRE(s);
+ REQUIRE(*s == value);
+}
diff --git a/nihil.posix/test.spawn.cc b/nihil.posix/test.spawn.cc
new file mode 100644
index 0000000..da321ff
--- /dev/null
+++ b/nihil.posix/test.spawn.cc
@@ -0,0 +1,117 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+TEST_CASE("spawn: system", "[spawn]")
+{
+ using namespace nihil;
+
+ auto exec = shell("x=1; echo $x");
+ REQUIRE(exec);
+
+ auto output = std::string();
+ auto capture = make_capture(stdout_fileno, output);
+ REQUIRE(capture);
+
+ auto proc = spawn(*exec, *capture);
+ REQUIRE(proc);
+
+ auto status = std::move(*proc).wait();
+ REQUIRE(status);
+
+ REQUIRE(status->okay());
+ REQUIRE(output == "1\n");
+}
+
+TEST_CASE("spawn: execv", "[spawn]") {
+ using namespace nihil;
+
+ auto args = argv({"sh", "-c", "x=1; echo $x"});
+ auto exec = execv("/bin/sh", std::move(args));
+ REQUIRE(exec);
+
+ auto output = std::string();
+ auto capture = make_capture(stdout_fileno, output);
+ REQUIRE(capture);
+
+ auto proc = spawn(*exec, *capture);
+ REQUIRE(proc);
+
+ auto status = std::move(*proc).wait();
+ REQUIRE(status);
+
+ REQUIRE(status->okay());
+ REQUIRE(output == "1\n");
+}
+
+TEST_CASE("spawn: execvp", "[spawn]") {
+ using namespace nihil;
+
+ auto args = argv({"sh", "-c", "x=1; echo $x"});
+ auto exec = execvp("sh", std::move(args));
+ REQUIRE(exec);
+
+ auto output = std::string();
+ auto capture = make_capture(stdout_fileno, output);
+ REQUIRE(capture);
+
+ auto proc = spawn(*exec, *capture);
+ REQUIRE(proc);
+
+ auto status = std::move(*proc).wait();
+ REQUIRE(status);
+
+ REQUIRE(status->okay());
+ REQUIRE(output == "1\n");
+}
+
+TEST_CASE("spawn: execl", "[spawn]") {
+ using namespace nihil;
+
+ auto exec = execl("/bin/sh", "sh", "-c", "x=1; echo $x");
+ REQUIRE(exec);
+
+ auto output = std::string();
+ auto capture = make_capture(stdout_fileno, output);
+ REQUIRE(capture);
+
+ auto proc = spawn(*exec, *capture);
+ REQUIRE(proc);
+
+ auto status = std::move(*proc).wait();
+ REQUIRE(status);
+
+ REQUIRE(status->okay());
+ REQUIRE(output == "1\n");
+}
+
+TEST_CASE("spawn: execlp", "[spawn]") {
+ using namespace nihil;
+
+ auto exec = execlp("sh", "sh", "-c", "x=1; echo $x");
+ REQUIRE(exec);
+
+ auto output = std::string();
+ auto capture = make_capture(stdout_fileno, output);
+ REQUIRE(capture);
+
+ auto proc = spawn(*exec, *capture);
+ REQUIRE(proc);
+
+ auto status = std::move(*proc).wait();
+ REQUIRE(status);
+
+ REQUIRE(status->okay());
+ REQUIRE(output == "1\n");
+}
+
+TEST_CASE("spawn: execlp failure", "[spawn]") {
+ using namespace nihil;
+
+ auto exec = execlp("nihil_no_such_executable", "x");
+ REQUIRE(!exec);
+}
diff --git a/nihil.posix/test.tempfile.cc b/nihil.posix/test.tempfile.cc
new file mode 100644
index 0000000..b1c7604
--- /dev/null
+++ b/nihil.posix/test.tempfile.cc
@@ -0,0 +1,90 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <filesystem>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+TEST_CASE("posix.tempfile: create", "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile();
+ REQUIRE(file);
+ REQUIRE(file->fd());
+
+ auto path = file->path();
+ REQUIRE(exists(path) == true);
+}
+
+TEST_CASE("posix.tempfile: create and release", "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile();
+ REQUIRE(file);
+ REQUIRE(file->fd());
+
+ auto path = file->path();
+ REQUIRE(exists(path) == true);
+
+ file->release();
+ REQUIRE(exists(path) == false);
+
+ REQUIRE_THROWS_AS(file->fd(), std::logic_error);
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+}
+
+TEST_CASE("posix.tempfile: create and double release", "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile();
+ REQUIRE(file->fd());
+
+ auto path = file->path();
+ REQUIRE(exists(path) == true);
+
+ file->release();
+ REQUIRE(exists(path) == false);
+
+ REQUIRE_THROWS_AS(file->fd(), std::logic_error);
+ REQUIRE_THROWS_AS(file->release(), std::logic_error);
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+}
+
+TEST_CASE("posix.tempfile: create unlinked", "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile(nihil::tempfile_unlink);
+ REQUIRE(file);
+ REQUIRE(file->fd());
+
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+}
+
+TEST_CASE("posix.tempfile: create unlinked and release",
+ "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile(nihil::tempfile_unlink);
+ REQUIRE(file);
+ REQUIRE(file->fd());
+
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+
+ file->release();
+
+ REQUIRE_THROWS_AS(file->fd(), std::logic_error);
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+}
+
+TEST_CASE("posix.tempfile: create unlinked and double release",
+ "[nihil][nihil.posix]")
+{
+ auto file = nihil::tempfile(nihil::tempfile_unlink);
+ REQUIRE(file->fd());
+
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+
+ file->release();
+
+ REQUIRE_THROWS_AS(file->fd(), std::logic_error);
+ REQUIRE_THROWS_AS(file->release(), std::logic_error);
+ REQUIRE_THROWS_AS(file->path(), std::logic_error);
+}
diff --git a/nihil.posix/write_file.ccm b/nihil.posix/write_file.ccm
new file mode 100644
index 0000000..867e0db
--- /dev/null
+++ b/nihil.posix/write_file.ccm
@@ -0,0 +1,82 @@
+/*
+ * 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>
+
+export module nihil.posix:write_file;
+
+import nihil.error;
+import nihil.guard;
+import nihil.monad;
+import :fd;
+import :open;
+import :rename;
+
+namespace nihil {
+
+/*
+ * 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,
+ int mode = 0777)
+ -> std::expected<std::size_t, error>
+{
+ auto file = co_await open(filename, O_CREAT|O_WRONLY, mode);
+ auto nbytes = co_await write(file, range);
+ co_return nbytes;
+}
+
+/*
+ * Utility wrapper for non-contiguous ranges.
+ */
+export [[nodiscard]]
+auto write_file(std::filesystem::path const &filename,
+ std::ranges::range auto &&range)
+ -> std::expected<std::size_t, error>
+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.
+ */
+export [[nodiscard]]
+auto safe_write_file(std::filesystem::path const &filename,
+ std::ranges::range auto &&range)
+ -> std::expected<void, error>
+{
+ auto tmpfile = filename;
+ tmpfile.remove_filename();
+ tmpfile /= (filename.filename().native() + ".tmp");
+
+ auto tmpfile_guard = guard([&tmpfile] {
+ ::unlink(tmpfile.c_str());
+ });
+
+ co_await write_file(tmpfile, range);
+ co_await nihil::rename(tmpfile, filename);
+
+ tmpfile_guard.release();
+ co_return {};
+}
+
+
+} // namespace nihil
diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt
new file mode 100644
index 0000000..9d8ab3a
--- /dev/null
+++ b/nihil.ucl/CMakeLists.txt
@@ -0,0 +1,46 @@
+# This source code is released into the public domain.
+
+pkg_check_modules(LIBUCL REQUIRED libucl)
+
+add_library(nihil.ucl STATIC)
+target_link_libraries(nihil.ucl PRIVATE 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
+ type.ccm
+
+ array.ccm
+ boolean.ccm
+ integer.ccm
+ 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})
+target_include_directories(nihil.ucl PUBLIC ${LIBUCL_INCLUDE_DIRS})
+target_link_libraries(nihil.ucl PUBLIC ${LIBUCL_LIBRARIES})
+target_link_directories(nihil.ucl PUBLIC ${LIBUCL_LIBRARY_DIRS})
+
+if(NIHIL_TESTS)
+ add_subdirectory(tests)
+ enable_testing()
+endif()
diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm
new file mode 100644
index 0000000..e3730ab
--- /dev/null
+++ b/nihil.ucl/array.ccm
@@ -0,0 +1,468 @@
+/*
+ * 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 :object;
+
+namespace nihil::ucl {
+
+export template<datatype T>
+struct array;
+
+export template<datatype T>
+struct array_iterator {
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using reference = T&;
+ using pointer = T*;
+
+ array_iterator() = default;
+
+ [[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");
+
+ 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");
+
+ return T(nihil::ucl::ref, uobj);
+ }
+
+ [[nodiscard]] auto operator[] (this array_iterator const &self,
+ difference_type idx)
+ -> T
+ {
+ return *(self + idx);
+ }
+
+ auto operator++ (this array_iterator &self) -> array_iterator &
+ {
+ auto arr = self.get_array();
+ if (self.m_idx == ::ucl_array_size(arr))
+ 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 copy = self;
+ ++self;
+ return copy;
+ }
+
+ 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");
+ --self.m_idx;
+ return self;
+ }
+
+ 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 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");
+
+ lhs.m_idx += rhs;
+ return lhs;
+ }
+
+ 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");
+ lhs.m_idx -= rhs;
+ return lhs;
+ }
+
+ [[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");
+
+ return lhs.m_idx - rhs.m_idx;
+ }
+
+private:
+ friend struct array<T>;
+
+ ::ucl_object_t const * m_array{};
+ std::size_t m_idx{};
+
+ [[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");
+
+ return self.m_array;
+ }
+
+ array_iterator(::ucl_object_t const *array, std::size_t 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;
+}
+
+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 = object>
+struct array final : object {
+ inline 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, [] {
+ auto *uobj = ::ucl_object_typed_new(UCL_ARRAY);
+ if (uobj == nullptr)
+ throw std::system_error(
+ std::make_error_code(std::errc(errno)));
+ 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.
+ */
+ 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;
+ }())
+ {
+ }
+
+ 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;
+ }())
+ {
+ }
+
+ /*
+ * 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()
+ {
+ // This is exception safe, because if we throw here the
+ // base class destructor will free the array.
+ while (first != last) {
+ push_back(*first);
+ ++first;
+ }
+ }
+
+ /*
+ * 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 initializer_list.
+ */
+ array(std::initializer_list<T> const &list)
+ : array(std::ranges::begin(list),
+ std::ranges::end(list))
+ {
+ }
+
+ /*
+ * Array iterator access.
+ */
+
+ [[nodiscard]] auto begin(this array const &self) -> iterator
+ {
+ return {self.get_ucl_object(), 0};
+ }
+
+ [[nodiscard]] auto end(this array const &self) -> iterator
+ {
+ return {self.get_ucl_object(), self.size()};
+ }
+
+ /*
+ * 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.
+ */
+ [[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
+ {
+ ::ucl_object_reserve(self.get_ucl_object(), nelems);
+ }
+
+ /*
+ * 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.
+ */
+ 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
+ {
+ 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");
+
+ return T(nihil::ucl::ref, uobj);
+ }
+
+ [[nodiscard]] auto operator[] (this array const &self, size_type idx) -> T
+ {
+ return self.at(idx);
+ }
+
+ /*
+ * Return the first element.
+ */
+ [[nodiscard]] auto front(this array const &self) -> T
+ {
+ return self.at(0);
+ }
+
+ /*
+ * 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");
+ 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;
+
+ for (typename array<T>::size_type i = 0; i < a.size(); ++i)
+ if (a.at(i) != b.at(i))
+ return false;
+
+ return true;
+}
+
+/*
+ * 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);
+}
+
+} // 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>
+struct std::formatter<nihil::ucl::array<T>, char>
+{
+ template<class ParseContext>
+ constexpr ParseContext::iterator parse(ParseContext& ctx)
+ {
+ return ctx.begin();
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::array<T> const &o,
+ FmtContext& ctx) const
+ {
+ auto it = ctx.out();
+ bool first = true;
+
+ *it++ = '[';
+
+ for (auto &&elm : o) {
+ if (first)
+ first = false;
+ else {
+ *it++ = ',';
+ *it++ = ' ';
+ }
+
+ it = std::format_to(it, "{}", elm);
+ }
+
+ *it++ = ']';
+ return it;
+ }
+};
diff --git a/nihil.ucl/boolean.cc b/nihil.ucl/boolean.cc
new file mode 100644
index 0000000..91f2b17
--- /dev/null
+++ b/nihil.ucl/boolean.cc
@@ -0,0 +1,106 @@
+/*
+ * 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
new file mode 100644
index 0000000..068dfdd
--- /dev/null
+++ b/nihil.ucl/boolean.ccm
@@ -0,0 +1,91 @@
+/*
+ * 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 :object;
+
+namespace nihil::ucl {
+
+export struct boolean final : object {
+ using contained_type = bool;
+
+ inline static constexpr object_type ucl_type = object_type::boolean;
+
+ /*
+ * Create a boolean holding the value false. Throws std::system_error
+ * on failure.
+ */
+ boolean();
+
+ /*
+ * Create a boolean holding a specific value. Throws std::system_error
+ * on failure.
+ */
+ explicit boolean(bool);
+
+ /*
+ * 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);
+
+ // Return this object's value.
+ auto value(this boolean const &self) -> contained_type;
+};
+
+/*
+ * Boolean constructors. These return an error instead of throwing.
+ */
+
+export [[nodiscard]] auto
+make_boolean(boolean::contained_type = false) -> std::expected<boolean, error>;
+
+/*
+ * Comparison operators.
+ */
+
+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;
+
+} // namespace nihil::ucl
+
+/*
+ * 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)
+ {
+ return base_formatter.parse(ctx);
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::boolean const &o,
+ FmtContext& ctx) const
+ {
+ return base_formatter.format(o.value(), ctx);
+ }
+};
diff --git a/nihil.ucl/emit.cc b/nihil.ucl/emit.cc
new file mode 100644
index 0000000..480ddd8
--- /dev/null
+++ b/nihil.ucl/emit.cc
@@ -0,0 +1,21 @@
+/*
+ * 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
new file mode 100644
index 0000000..b88f8e7
--- /dev/null
+++ b/nihil.ucl/emit.ccm
@@ -0,0 +1,209 @@
+/*
+ * 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 :object;
+
+namespace nihil::ucl {
+
+export enum struct emitter {
+ 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);
+
+ return 0;
+ } catch (...) {
+ return 0;
+ }
+
+ static auto append_len(unsigned char const *str, std::size_t len,
+ void *ud)
+ noexcept -> int
+ try {
+ auto *self = static_cast<emit_wrapper *>(ud);
+
+ for (auto c : std::span(str, len))
+ *self->iterator++ = static_cast<char>(c);
+
+ return 0;
+ } catch (...) {
+ return 0;
+ }
+
+ static auto append_int(std::int64_t value, void *ud)
+ noexcept -> int
+ try {
+ auto constexpr bufsize =
+ std::numeric_limits<std::int64_t>::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, 10);
+
+ if (result.ec == std::errc())
+ for (auto c : std::span(buf.data(), result.ptr))
+ *self->iterator++ = c;
+
+ return 0;
+ } catch (...) {
+ return 0;
+ }
+
+ static auto append_double(double value, void *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);
+
+ if (result.ec == std::errc())
+ for (auto c : std::span(buf.data(), result.ptr))
+ *self->iterator++ = c;
+
+ return 0;
+ } catch (...) {
+ return 0;
+ }
+
+ auto get_functions(this emit_wrapper &self) -> ucl_emitter_functions
+ {
+ auto ret = ucl_emitter_functions{};
+
+ ret.ucl_emitter_append_character = &emit_wrapper::append_character;
+ ret.ucl_emitter_append_len = &emit_wrapper::append_len;
+ ret.ucl_emitter_append_int = &emit_wrapper::append_int;
+ ret.ucl_emitter_append_double = &emit_wrapper::append_double;
+ ret.ud = &self;
+
+ return ret;
+ }
+
+private:
+ Iterator iterator{};
+};
+
+export auto emit(object const &object, emitter 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);
+}
+
+/*
+ * 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 &;
+
+} // namespace nihil::ucl
+
+/*
+ * Specialisation of std::formatter<> for object.
+ */
+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)
+ {
+ auto it = ctx.begin();
+ auto end = ctx.end();
+
+ while (it != end) {
+ switch (*it) {
+ case 'j':
+ emitter = nihil::ucl::emitter::json;
+ break;
+ case 'J':
+ emitter = nihil::ucl::emitter::compact_json;
+ break;
+ case 'c':
+ emitter = nihil::ucl::emitter::configuration;
+ break;
+ case 'y':
+ emitter = nihil::ucl::emitter::yaml;
+ break;
+ case '}':
+ return it;
+ default:
+ throw std::format_error("Invalid format string "
+ "for UCL object");
+ }
+
+ ++it;
+ }
+
+ return it;
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::object const &o,
+ FmtContext& ctx) const
+ {
+ // We can't use emit() here since the context iterator is not
+ // an std::output_iterator.
+
+ auto out = ctx.out();
+
+ auto ucl_format = static_cast<::ucl_emitter>(emitter);
+ 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;
+ }
+};
diff --git a/nihil.ucl/errc.cc b/nihil.ucl/errc.cc
new file mode 100644
index 0000000..0b65b86
--- /dev/null
+++ b/nihil.ucl/errc.cc
@@ -0,0 +1,49 @@
+/*
+ * 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
new file mode 100644
index 0000000..8f0444d
--- /dev/null
+++ b/nihil.ucl/errc.ccm
@@ -0,0 +1,33 @@
+/*
+ * 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
new file mode 100644
index 0000000..825d8f6
--- /dev/null
+++ b/nihil.ucl/integer.cc
@@ -0,0 +1,102 @@
+/*
+ * 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
new file mode 100644
index 0000000..e35a471
--- /dev/null
+++ b/nihil.ucl/integer.ccm
@@ -0,0 +1,115 @@
+/*
+ * 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 :object;
+import :type;
+
+namespace nihil::ucl {
+
+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);
+
+ // Return the value of this object.
+ [[nodiscard]] auto value(this integer const &self) -> contained_type;
+};
+
+/*
+ * Integer constructors. These return an error instead of throwing.
+ */
+
+export [[nodiscard]] auto
+make_integer(integer::contained_type = 0) -> std::expected<integer, error>;
+
+/*
+ * Comparison operators.
+ */
+
+export [[nodiscard]] auto operator== (integer const &a,
+ integer const &b) -> bool;
+
+export [[nodiscard]] auto operator== (integer const &a,
+ integer::contained_type b) -> bool;
+
+export [[nodiscard]] auto operator<=> (integer const &a,
+ integer const &b)
+ -> std::strong_ordering;
+
+export [[nodiscard]] auto operator<=> (integer const &a,
+ integer::contained_type b)
+ -> std::strong_ordering;
+
+/*
+ * Literal operator.
+ */
+inline namespace literals {
+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 nihil::ucl
+
+namespace nihil { inline namespace literals {
+ export using namespace ::nihil::ucl::literals;
+}} // namespace nihil::literals
+
+/*
+ * 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)
+ {
+ return base_formatter.parse(ctx);
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::integer const &o,
+ FmtContext& ctx) const
+ {
+ return base_formatter.format(o.value(), ctx);
+ }
+};
diff --git a/nihil.ucl/map.ccm b/nihil.ucl/map.ccm
new file mode 100644
index 0000000..fa77601
--- /dev/null
+++ b/nihil.ucl/map.ccm
@@ -0,0 +1,293 @@
+/*
+ * 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 :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)
+ : 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>
+struct map;
+
+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 &;
+ using const_reference = value_type const &;
+ using pointer = value_type *;
+ using const_pointer = value_type const *;
+
+ struct sentinel{};
+
+ [[nodiscard]] auto operator==(this map_iterator const &self, sentinel)
+ -> bool
+ {
+ return (self.m_state->cur == nullptr);
+ }
+
+ auto operator++(this map_iterator &self) -> map_iterator &
+ {
+ self.m_state->next();
+ return self;
+ }
+
+ auto operator++(this map_iterator &self, int) -> map_iterator &
+ {
+ self.m_state->next();
+ return self;
+ }
+
+ [[nodiscard]] auto operator*(this map_iterator const &self)
+ -> value_type
+ {
+ auto obj = T(ref, self.m_state->cur);
+ return {obj.key(), std::move(obj)};
+ }
+
+private:
+ friend struct map<T>;
+
+ map_iterator(::ucl_object_t const *obj)
+ : m_state(std::make_shared<state>(obj))
+ {
+ ++(*this);
+ }
+
+ struct state {
+ state(::ucl_object_t const *obj)
+ {
+ 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;
+
+ ~state()
+ {
+ if (iter != nullptr)
+ ::ucl_object_iterate_free(iter);
+ }
+
+ auto next() -> void
+ {
+ cur = ::ucl_object_iterate_safe(iter, true);
+ }
+
+ ucl_object_iter_t iter = nullptr;
+ ucl_object_t const *cur = 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;
+
+ 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>;
+
+ /*
+ * 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)));
+ 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;
+ }())
+ {
+ 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;
+ }())
+ {
+ }
+
+ /*
+ * 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()
+ {
+ // This is exception safe, because if we throw here the
+ // base class destructor will free the map.
+ while (first != last) {
+ insert(*first);
+ ++first;
+ }
+ }
+
+ /*
+ * 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 initializer_list.
+ */
+ map(std::initializer_list<value_type> const &list)
+ : map(std::ranges::begin(list), std::ranges::end(list))
+ {
+ }
+
+ /*
+ * Map iterator access.
+ */
+
+ [[nodiscard]] auto begin(this map const &self) -> iterator
+ {
+ return {self.get_ucl_object()};
+ }
+
+ [[nodiscard]] auto end(this map const &) -> iterator::sentinel
+ {
+ return {};
+ }
+
+ /*
+ * Reserve space for future insertions.
+ */
+ auto reserve(this map &self, size_type nelems) -> void
+ {
+ ::ucl_object_reserve(self.get_ucl_object(), nelems);
+ }
+
+ /*
+ * 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);
+ }
+
+ /*
+ * Access a map element by key.
+ */
+ [[nodiscard]] auto find(this map const &self, std::string_view key)
+ -> std::optional<T>
+ {
+ 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
+ {
+ 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>
+ {
+ 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
+ {
+ auto obj = self.find(key);
+ if (obj)
+ return *obj;
+ throw key_not_found(key);
+ }
+};
+
+} // namespace nihil::ucl
diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm
new file mode 100644
index 0000000..b16eb3d
--- /dev/null
+++ b/nihil.ucl/nihil.ucl.ccm
@@ -0,0 +1,21 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil.ucl;
+
+export import :emit;
+export import :errc;
+export import :object;
+export import :object_cast;
+export import :parser;
+export import :type;
+
+export import :array;
+export import :boolean;
+export import :integer;
+export import :map;
+export import :real;
+export import :string;
diff --git a/nihil.ucl/object.cc b/nihil.ucl/object.cc
new file mode 100644
index 0000000..53fc4c7
--- /dev/null
+++ b/nihil.ucl/object.cc
@@ -0,0 +1,114 @@
+/*
+ * 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
new file mode 100644
index 0000000..9a7eaf7
--- /dev/null
+++ b/nihil.ucl/object.ccm
@@ -0,0 +1,88 @@
+/*
+ * 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>
+
+#include <ucl.h>
+
+export module nihil.ucl:object;
+
+import :type;
+
+namespace nihil::ucl {
+
+/***********************************************************************
+ * The basic object type.
+ */
+
+// Ref the UCL object when creating an object.
+export inline constexpr struct ref_t {} ref;
+// Don't ref the UCL object.
+export inline constexpr struct noref_t {} noref;
+
+export struct object {
+ inline 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);
+
+ // Free our object on destruction.
+ virtual ~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 &;
+
+ // Increase the refcount of this object.
+ [[nodiscard]] auto ref(this object const &self) -> object;
+
+ // Return the type of this object.
+ [[nodiscard]] auto type(this object const &self) -> object_type;
+
+ // 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 *;
+
+ // Return the key of this object.
+ [[nodiscard]] auto key(this object const &self) -> std::string_view;
+
+protected:
+ // The object we're wrapping.
+ ::ucl_object_t *m_object = nullptr;
+
+ friend auto swap(object &a, object &b) -> void;
+};
+
+/***********************************************************************
+ * Object comparison.
+ */
+
+export [[nodiscard]] auto operator==(object const &lhs, object const &rhs)
+ -> bool;
+
+export [[nodiscard]] auto operator<=>(object const &lhs, object const &rhs)
+ -> std::strong_ordering;
+
+} // namespace nihil::ucl
diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm
new file mode 100644
index 0000000..3fa9eba
--- /dev/null
+++ b/nihil.ucl/object_cast.ccm
@@ -0,0 +1,89 @@
+/*
+ * 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.monad;
+import :type;
+import :object;
+import :array;
+
+namespace nihil::ucl {
+
+/*
+ * Ensure a UCL object is convertible to another type. Throws type_mismatch
+ * if not.
+ */
+
+// Implementation for basic types.
+template<datatype To>
+struct convert_check
+{
+ [[nodiscard]] auto check(::ucl_object_t const *from)
+ -> std::expected<void, type_mismatch>
+ {
+ auto from_type = static_cast<object_type>(::ucl_object_type(from));
+ auto to_type = To::ucl_type;
+
+ // Converting from anything to object is permitted.
+ if (to_type == object_type::object)
+ return {};
+
+ // Converting between two equal types is permitted.
+ if (from_type == to_type)
+ return {};
+
+ // Otherwise, this is an error.
+ return std::unexpected(type_mismatch(to_type, from_type));
+ }
+};
+
+// Implementation for array.
+template<typename T>
+struct convert_check<array<T>>
+{
+ [[nodiscard]] auto check(::ucl_object_t const *from)
+ -> std::expected<void, type_mismatch>
+ {
+ using To = array<T>;
+ auto from_type = static_cast<object_type>(::ucl_object_type(from));
+ auto to_type = To::ucl_type;
+
+ // If the source type is not an array, this is an error.
+ if (from_type != object_type::array)
+ co_return std::unexpected(
+ type_mismatch(to_type, from_type));
+
+ for (std::size_t i = 0, size = ::ucl_array_size(from);
+ i < size; ++i) {
+ auto const *arr_obj = ::ucl_array_find_index(from, i);
+ co_await convert_check<typename To::value_type>{}
+ .check(arr_obj);
+ }
+
+ co_return {};
+ }
+};
+
+/*
+ * Convert a UCL object to another type.
+ */
+export template<datatype To>
+auto object_cast(object const &from) -> std::expected<To, type_mismatch>
+{
+ auto uobj = from.get_ucl_object();
+
+ co_await convert_check<To>{}.check(uobj);
+ co_return To(nihil::ucl::ref, uobj);
+}
+
+} // namespace nihil::ucl
diff --git a/nihil.ucl/parser.cc b/nihil.ucl/parser.cc
new file mode 100644
index 0000000..0a08670
--- /dev/null
+++ b/nihil.ucl/parser.cc
@@ -0,0 +1,102 @@
+/*
+ * 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
new file mode 100644
index 0000000..5fa3495
--- /dev/null
+++ b/nihil.ucl/parser.ccm
@@ -0,0 +1,160 @@
+/*
+ * 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.monad;
+import :object;
+import :map;
+
+namespace nihil::ucl {
+
+// UCL parser flags.
+export inline constexpr int parser_key_lower = UCL_PARSER_KEY_LOWERCASE;
+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);
+
+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;
+};
+
+/*
+ * 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 *);
+
+ // Destroy our parser when we're destroyed.
+ ~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 &;
+
+ // 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>>)
+ {
+ 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());
+
+ 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;
+
+ // Add data to the parser.
+ [[nodiscard]] auto add(this parser &self,
+ std::ranges::contiguous_range auto &&data)
+ -> std::expected<void, error>
+ // 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 ret = ::ucl_parser_add_chunk(
+ p, dptr, std::ranges::size(data));
+
+ if (ret == true)
+ return {};
+
+ 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)>)
+ {
+ 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>;
+
+ // Return the stored parser object.
+ [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser *;
+
+private:
+ // The parser object. Should never be null, unless we've been
+ // moved-from.
+ ucl_parser *m_parser;
+
+ // Functions added by register_macro. We have to store these as
+ // pointers because we pass the address to libucl.
+ std::vector<std::unique_ptr<macro_handler>> m_macros;
+};
+
+// Create a parser with the given flags.
+export [[nodiscard]] auto
+make_parser(int flags = 0) -> std::expected<parser, error>;
+
+// 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>
+{
+ 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>
+{
+ co_return co_await parse(0, std::forward<decltype(data)>(data));
+}
+
+} // namespace nihil::ucl
diff --git a/nihil.ucl/real.cc b/nihil.ucl/real.cc
new file mode 100644
index 0000000..6d9e082
--- /dev/null
+++ b/nihil.ucl/real.cc
@@ -0,0 +1,104 @@
+/*
+ * 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
new file mode 100644
index 0000000..f425a9a
--- /dev/null
+++ b/nihil.ucl/real.ccm
@@ -0,0 +1,112 @@
+/*
+ * 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 :object;
+import :type;
+
+namespace nihil::ucl {
+
+export struct real final : object {
+ using contained_type = double;
+
+ inline 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 a specific value. Throws std::system_error
+ * on failure.
+ */
+ explicit real(contained_type value);
+
+ /*
+ * 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);
+
+ // Return the value of this real.
+ [[nodiscard]] auto value(this real const &self) -> contained_type;
+};
+
+/*
+ * Real constructors. These return an error instead of throwing.
+ */
+
+export [[nodiscard]] auto
+make_real(real::contained_type = 0) -> std::expected<real, error>;
+
+/*
+ * Comparison operators.
+ */
+
+export [[nodiscard]] auto operator== (real const &a, real const &b) -> bool;
+
+export [[nodiscard]] auto operator== (real const &a,
+ real::contained_type b) -> bool;
+
+export [[nodiscard]] auto operator<=> (real const &a, real const &b)
+ -> std::partial_ordering;
+
+export [[nodiscard]] auto operator<=> (real const &a, real::contained_type b)
+ -> std::partial_ordering;
+
+/*
+ * Literal operator.
+ */
+inline namespace literals {
+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()))
+ throw std::out_of_range("literal out of range");
+
+ return real(static_cast<double>(d));
+}
+} // namespace nihil::ucl::literals
+
+} // namespace nihil::ucl
+
+namespace nihil { inline namespace literals {
+ export using namespace ::nihil::ucl::literals;
+}} // namespace nihil::literals
+
+/*
+ * 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)
+ {
+ return base_formatter.parse(ctx);
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::real const &o,
+ FmtContext& ctx) const
+ {
+ return base_formatter.format(o.value(), ctx);
+ }
+};
diff --git a/nihil.ucl/string.cc b/nihil.ucl/string.cc
new file mode 100644
index 0000000..67e97f4
--- /dev/null
+++ b/nihil.ucl/string.cc
@@ -0,0 +1,187 @@
+/*
+ * 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
new file mode 100644
index 0000000..c757bf1
--- /dev/null
+++ b/nihil.ucl/string.ccm
@@ -0,0 +1,229 @@
+/*
+ * 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 :object;
+import :type;
+
+namespace nihil::ucl {
+
+export struct string final : object {
+ using contained_type = std::string_view;
+ inline static constexpr object_type ucl_type = object_type::string;
+
+ // string is a container of char
+ using value_type = char const;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using reference = value_type &;
+ 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>>)
+ 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);
+ string(noref_t, ::ucl_object_t *uobj);
+
+ // Return the value of this string.
+ [[nodiscard]] auto value(this string const &self) -> contained_type;
+
+ // Return the size of this string.
+ [[nodiscard]] auto size(this string const &self) -> size_type;
+
+ // Test if this string is empty.
+ [[nodiscard]] auto empty(this string const &self) -> bool;
+
+ // Access this string's data
+ [[nodiscard]] auto data(this string const &self) -> pointer;
+
+ // Iterator access
+ [[nodiscard]] auto begin(this string const &self) -> iterator;
+ [[nodiscard]] auto end(this string const &self) -> iterator;
+};
+
+/*
+ * String constructors. These return an error instead of throwing.
+ */
+
+// Empty string
+export [[nodiscard]] auto
+make_string() -> std::expected<string, error>;
+
+// From string_view
+export [[nodiscard]] auto
+make_string(std::string_view) -> std::expected<string, error>;
+
+// From C literal
+export [[nodiscard]] auto
+make_string(char const *) -> std::expected<string, error>;
+
+// 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>>)
+[[nodiscard]] auto make_string(Range &&range)
+{
+ return make_string(std::string_view(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>>)
+[[nodiscard]] auto make_string(Range &&range)
+{
+ return make_string(std::string(std::from_range, range));
+}
+
+// From iterator pair
+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 &;
+
+/*
+ * 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
+
+} // namespace nihil::ucl
+
+namespace nihil { inline namespace literals {
+ export using namespace ::nihil::ucl::literals;
+}} // namespace nihil::literals
+
+/*
+ * std::formatter for a string. This provides the same format operations
+ * as std::formatter<std::string_view>.
+ */
+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)
+ {
+ return base_formatter.parse(ctx);
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::string const &o,
+ FmtContext& ctx) const
+ {
+ return base_formatter.format(o.value(), ctx);
+ }
+};
diff --git a/nihil.ucl/tests/CMakeLists.txt b/nihil.ucl/tests/CMakeLists.txt
new file mode 100644
index 0000000..0257b4f
--- /dev/null
+++ b/nihil.ucl/tests/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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)
+
+find_package(Catch2 REQUIRED)
+
+include(CTest)
+include(Catch)
+catch_discover_tests(nihil.ucl.test)
diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/tests/array.cc
new file mode 100644
index 0000000..866fa45
--- /dev/null
+++ b/nihil.ucl/tests/array.cc
@@ -0,0 +1,478 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <algorithm>
+#include <concepts>
+#include <expected>
+#include <ranges>
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: array: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ REQUIRE(array<>::ucl_type == object_type::array);
+ REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY);
+
+ static_assert(std::destructible<array<>>);
+ static_assert(std::default_initializable<array<>>);
+ static_assert(std::move_constructible<array<>>);
+ static_assert(std::copy_constructible<array<>>);
+ 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>);
+}
+
+TEST_CASE("ucl: array: constructor", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default") {
+ auto arr = array<integer>();
+ REQUIRE(arr.size() == 0);
+ REQUIRE(str(arr.type()) == "array");
+ }
+
+ SECTION("from range") {
+ auto vec = std::vector{integer(1), integer(42)};
+ auto arr = array<integer>(std::from_range, vec);
+
+ REQUIRE(arr.size() == 2);
+ REQUIRE(arr[0] == 1);
+ REQUIRE(arr[1] == 42);
+ }
+
+ SECTION("from iterator pair") {
+ auto vec = std::vector{integer(1), integer(42)};
+ auto arr = array<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") {
+ auto arr = array<integer>{integer(1), integer(42)};
+
+ REQUIRE(arr.size() == 2);
+ REQUIRE(arr[0] == 1);
+ REQUIRE(arr[1] == 42);
+ }
+}
+
+TEST_CASE("ucl: array: construct from UCL object", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("ref, correct type") {
+ auto uarr = ::ucl_object_typed_new(UCL_ARRAY);
+ auto uint = ::ucl_object_fromint(42);
+ ::ucl_array_append(uarr, uint);
+
+ auto arr = array<integer>(ref, uarr);
+ REQUIRE(arr[0] == 42);
+
+ ::ucl_object_unref(uarr);
+ }
+
+ SECTION("noref, correct type") {
+ auto uarr = ::ucl_object_typed_new(UCL_ARRAY);
+ auto uint = ::ucl_object_fromint(42);
+ ::ucl_array_append(uarr, uint);
+
+ auto arr = array<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);
+ ::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);
+
+ REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, wrong type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+}
+
+TEST_CASE("ucl: array: swap", "[ucl]")
+{
+ // do not add using namespace nihil::ucl
+
+ auto arr1 = nihil::ucl::array<nihil::ucl::integer>{
+ nihil::ucl::integer(1),
+ nihil::ucl::integer(2)
+ };
+
+ auto arr2 = nihil::ucl::array<nihil::ucl::integer>{
+ nihil::ucl::integer(3),
+ };
+
+ swap(arr1, arr2);
+
+ REQUIRE(arr1.size() == 1);
+ REQUIRE(arr1[0] == 3);
+
+ REQUIRE(arr2.size() == 2);
+ REQUIRE(arr2[0] == 1);
+}
+
+TEST_CASE("ucl: array: push_back", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>();
+ REQUIRE(arr.size() == 0);
+
+ arr.push_back(integer(1));
+ arr.push_back(integer(42));
+ arr.push_back(integer(666));
+
+ REQUIRE(arr.size() == 3);
+ REQUIRE(arr[0] == 1);
+ REQUIRE(arr[1] == 42);
+ REQUIRE(arr[2] == 666);
+
+ REQUIRE_THROWS_AS(arr[3], std::out_of_range);
+
+ REQUIRE(arr.front() == 1);
+ REQUIRE(arr.back() == 666);
+}
+
+TEST_CASE("ucl: array: compare", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>{
+ integer(1), integer(42), integer(666)
+ };
+
+ auto arr2 = array<integer>();
+ REQUIRE(arr != arr2);
+
+ arr2.push_back(integer(1));
+ arr2.push_back(integer(42));
+ arr2.push_back(integer(666));
+ REQUIRE(arr == arr2);
+
+ auto arr3 = array<integer>{
+ integer(1), integer(1), integer(1)
+ };
+
+ REQUIRE(arr != arr3);
+}
+
+TEST_CASE("ucl: array: iterator", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>{integer(1), integer(42), integer(666)};
+
+ auto it = arr.begin();
+ REQUIRE(*it == 1);
+ auto end = arr.end();
+ REQUIRE(it != end);
+ REQUIRE(it < end);
+
+ ++it;
+ REQUIRE(*it == 42);
+
+ ++it;
+ REQUIRE(*it == 666);
+
+ --it;
+ REQUIRE(*it == 42);
+
+ ++it;
+ REQUIRE(it != end);
+ ++it;
+ REQUIRE(it == end);
+}
+
+TEST_CASE("ucl: array: parse", "[ucl]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto obj = parse("value = [1, 42, 666]"sv).value();
+
+ auto arr = object_cast<array<integer>>(obj["value"]).value();
+
+ REQUIRE(arr.size() == 3);
+ REQUIRE(arr[0] == 1);
+ REQUIRE(arr[1] == 42);
+ REQUIRE(arr[2] == 666);
+}
+
+TEST_CASE("ucl: array: emit", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto ucl = parse("array = [1, 42, 666];").value();
+
+ auto output = std::format("{:c}", ucl);
+ REQUIRE(output ==
+"array [\n"
+" 1,\n"
+" 42,\n"
+" 666,\n"
+"]\n");
+}
+
+TEST_CASE("ucl: array: format", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("empty array") {
+ auto arr = array<integer>();
+ REQUIRE(std::format("{}", arr) == "[]");
+ }
+
+ 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") {
+ auto ucl = parse("array = [1, 42, 666];").value();
+ auto arr = object_cast<array<integer>>(ucl["array"]).value();
+
+ auto output = std::format("{}", arr);
+ REQUIRE(output == "[1, 42, 666]");
+ }
+}
+
+TEST_CASE("ucl: array: print to ostream", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("empty array") {
+ auto arr = array<integer>();
+ auto strm = std::ostringstream();
+ strm << arr;
+
+ REQUIRE(strm.str() == "[]");
+ }
+
+ 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") {
+ auto ucl = parse("array = [1, 42, 666];").value();
+ auto arr = object_cast<array<integer>>(ucl["array"]).value();
+ auto strm = std::ostringstream();
+ strm << arr;
+
+ REQUIRE(strm.str() == "[1, 42, 666]");
+ }
+}
+
+TEST_CASE("ucl: array is a sized_range", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>{integer(1), integer(42), integer(666)};
+
+ auto size = std::ranges::size(arr);
+ REQUIRE(size == 3);
+
+ auto begin = std::ranges::begin(arr);
+ static_assert(std::random_access_iterator<decltype(begin)>);
+
+ auto end = std::ranges::end(arr);
+ static_assert(std::sentinel_for<decltype(end), decltype(begin)>);
+
+ REQUIRE(std::distance(begin, end) == 3);
+
+ auto vec = std::vector<integer>();
+ std::ranges::copy(arr, std::back_inserter(vec));
+ REQUIRE(std::ranges::equal(arr, vec));
+
+ auto arr_as_ints =
+ arr | std::views::transform(&integer::value);
+ auto int_vec = std::vector<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]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>();
+
+ auto cast_ok = object_cast<integer>(arr);
+ REQUIRE(!cast_ok);
+}
+
+TEST_CASE("ucl: array: heterogeneous elements", "[ucl]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto obj_err = parse("array [ 42, true, \"test\" ];");
+ REQUIRE(obj_err);
+ auto obj = *obj_err;
+
+ auto err = object_cast<array<>>(obj["array"]);
+ REQUIRE(err);
+
+ auto arr = *err;
+ REQUIRE(arr.size() == 3);
+
+ auto int_obj = object_cast<integer>(arr[0]);
+ REQUIRE(int_obj);
+ REQUIRE(*int_obj == 42);
+
+ auto bool_obj = object_cast<boolean>(arr[1]);
+ REQUIRE(bool_obj);
+ REQUIRE(*bool_obj == true);
+
+ auto string_obj = object_cast<string>(arr[2]);
+ REQUIRE(string_obj);
+ REQUIRE(*string_obj == "test");
+}
+
+TEST_CASE("ucl: array: heterogenous cast", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<>();
+ arr.push_back(integer(42));
+ arr.push_back(boolean(true));
+
+ // Converting to an array<integer> should fail.
+ auto cast_ok = object_cast<array<integer>>(arr);
+ REQUIRE(!cast_ok);
+
+ // Converting to array<object> should succeed.
+ auto err = object_cast<array<object>>(arr);
+ REQUIRE(err);
+
+ auto obj_arr = *err;
+ REQUIRE(obj_arr[0] == integer(42));
+}
+
+TEST_CASE("ucl: array: homogeneous cast", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<>();
+ arr.push_back(integer(1));
+ arr.push_back(integer(42));
+
+ auto obj = object(ref, arr.get_ucl_object());
+
+ // Converting to array<string> should fail.
+ auto cast_ok = object_cast<array<string>>(obj);
+ REQUIRE(!cast_ok);
+
+ // Converting to an array<integer> should succeed.
+ auto err = object_cast<array<integer>>(obj);
+ REQUIRE(err);
+
+ auto obj_arr = *err;
+ REQUIRE(obj_arr[0] == 1);
+ REQUIRE(obj_arr[1] == 42);
+}
+
+TEST_CASE("array iterator: empty iterator", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto it = array_iterator<integer>();
+
+ REQUIRE_THROWS_AS(*it, std::logic_error);
+ REQUIRE_THROWS_AS(it[0], std::logic_error);
+ REQUIRE_THROWS_AS(it++, std::logic_error);
+ REQUIRE_THROWS_AS(++it, std::logic_error);
+
+ auto it2 = array_iterator<integer>();
+ REQUIRE(it == it2);
+ REQUIRE((it < it2) == false);
+ REQUIRE((it > it2) == false);
+}
+
+TEST_CASE("array iterator: invalid operations", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto arr = array<integer>{ integer(42) };
+ auto it = arr.begin();
+
+ SECTION("decrement before start") {
+ REQUIRE_THROWS_AS(--it, std::logic_error);
+ REQUIRE_THROWS_AS(it--, std::logic_error);
+ REQUIRE_THROWS_AS(it - 1, std::logic_error);
+ }
+
+ SECTION("increment past end") {
+ ++it;
+ REQUIRE(it == arr.end());
+
+ REQUIRE_THROWS_AS(++it, std::logic_error);
+ REQUIRE_THROWS_AS(it++, std::logic_error);
+ REQUIRE_THROWS_AS(it + 1, std::logic_error);
+ }
+
+ SECTION("dereference iterator at end") {
+ REQUIRE_THROWS_AS(it[1], std::logic_error);
+
+ ++it;
+ REQUIRE(it == arr.end());
+
+ REQUIRE_THROWS_AS(*it, std::logic_error);
+ }
+
+ SECTION("compare with different array") {
+ auto arr2 = array<integer>{ integer(42) };
+ REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error);
+ REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error);
+ REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error);
+ }
+
+ SECTION("compare with empty iterator") {
+ auto it2 = array_iterator<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);
+ }
+}
diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/tests/boolean.cc
new file mode 100644
index 0000000..f7ef95e
--- /dev/null
+++ b/nihil.ucl/tests/boolean.cc
@@ -0,0 +1,224 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: boolean: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ static_assert(std::same_as<bool, boolean::contained_type>);
+ REQUIRE(boolean::ucl_type == object_type::boolean);
+ REQUIRE(static_cast<::ucl_type>(boolean::ucl_type) == UCL_BOOLEAN);
+
+ static_assert(std::destructible<boolean>);
+ static_assert(std::default_initializable<boolean>);
+ static_assert(std::move_constructible<boolean>);
+ static_assert(std::copy_constructible<boolean>);
+ static_assert(std::equality_comparable<boolean>);
+ static_assert(std::totally_ordered<boolean>);
+ static_assert(std::swappable<boolean>);
+}
+
+TEST_CASE("ucl: boolean: constructor", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default") {
+ auto b = boolean();
+ REQUIRE(b == false);
+ }
+
+ SECTION("with value") {
+ auto b = boolean(true);
+ REQUIRE(b == true);
+ }
+}
+
+TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("ref, correct type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ auto i = boolean(ref, uobj);
+ REQUIRE(i == true);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, correct type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ auto i = boolean(noref, uobj);
+ REQUIRE(i == true);
+ }
+
+ SECTION("ref, wrong type") {
+ auto uobj = ::ucl_object_fromint(1);
+
+ REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, wrong type") {
+ auto uobj = ::ucl_object_fromint(1);
+
+ REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+}
+
+TEST_CASE("ucl: boolean: make_boolean", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default value") {
+ auto b = make_boolean().value();
+ REQUIRE(b == false);
+ }
+
+ SECTION("explicit value") {
+ auto b = make_boolean(true).value();
+ REQUIRE(b == true);
+ }
+}
+
+TEST_CASE("ucl: boolean: swap", "[ucl]")
+{
+ // do not add using namespace nihil::ucl
+
+ auto b1 = nihil::ucl::boolean(true);
+ auto b2 = nihil::ucl::boolean(false);
+
+ swap(b1, b2);
+
+ REQUIRE(b1 == false);
+ REQUIRE(b2 == true);
+}
+
+TEST_CASE("ucl: boolean: value()", "[ucl]")
+{
+ auto b = nihil::ucl::boolean(true);
+ REQUIRE(b.value() == true);
+}
+
+TEST_CASE("ucl: boolean: key()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto err = parse("a_bool = true");
+ REQUIRE(err);
+
+ auto obj = *err;
+ REQUIRE(object_cast<boolean>(obj["a_bool"])->key() == "a_bool");
+
+ auto b = nihil::ucl::boolean(true);
+ REQUIRE(b.key() == "");
+}
+
+TEST_CASE("ucl: boolean: comparison", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto b = boolean(true);
+
+ SECTION("operator==") {
+ REQUIRE(b == true);
+ REQUIRE(b == boolean(true));
+ }
+
+ SECTION("operator!=") {
+ REQUIRE(b != false);
+ REQUIRE(b != boolean(false));
+ }
+
+ SECTION("operator<") {
+ REQUIRE(b <= true);
+ REQUIRE(b <= nihil::ucl::boolean(true));
+ }
+
+ SECTION("operator>") {
+ REQUIRE(b > false);
+ REQUIRE(b > nihil::ucl::boolean(false));
+ }
+}
+
+TEST_CASE("ucl: boolean: parse", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto obj = parse("value = true").value();
+
+ auto v = obj["value"];
+ REQUIRE(v.key() == "value");
+ REQUIRE(object_cast<boolean>(v).value() == true);
+}
+
+TEST_CASE("ucl: boolean: parse and emit", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto ucl = parse("bool = true;").value();
+
+ auto output = std::string();
+ emit(ucl, nihil::ucl::emitter::configuration,
+ std::back_inserter(output));
+
+ REQUIRE(output == "bool = true;\n");
+}
+
+TEST_CASE("ucl: boolean: format", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare boolean") {
+ auto str = std::format("{}", boolean(true));
+ REQUIRE(str == "true");
+ }
+
+ SECTION("parsed boolean") {
+ auto obj = parse("bool = true;").value();
+ auto b = object_cast<boolean>(obj["bool"]).value();
+
+ auto str = std::format("{}", b);
+ REQUIRE(str == "true");
+ }
+
+ SECTION("with format string") {
+ auto str = std::format("{: >5}", boolean(true));
+ REQUIRE(str == " true");
+ }
+}
+
+TEST_CASE("ucl: boolean: print to ostream", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare boolean") {
+ auto strm = std::ostringstream();
+ strm << boolean(true);
+
+ REQUIRE(strm.str() == "true");
+ }
+
+ SECTION("parsed boolean") {
+ auto obj = parse("bool = true;").value();
+ auto i = object_cast<boolean>(obj["bool"]).value();
+
+ auto strm = std::ostringstream();
+ strm << i;
+
+ REQUIRE(strm.str() == "true");
+ }
+}
diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/tests/emit.cc
new file mode 100644
index 0000000..a7dcd71
--- /dev/null
+++ b/nihil.ucl/tests/emit.cc
@@ -0,0 +1,93 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <format>
+#include <sstream>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: emit to std::ostream", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
+ auto strm = std::ostringstream();
+ strm << *obj;
+
+ // The ostream emitter produces JSON.
+ REQUIRE(strm.str() == std::format("{:j}", *obj));
+}
+
+TEST_CASE("ucl: emit JSON with std::format", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
+ auto str = std::format("{:j}", *obj);
+
+ REQUIRE(str ==
+"{\n"
+" \"int\": [\n"
+" 1,\n"
+" 42,\n"
+" 666\n"
+" ]\n"
+"}");
+
+ // Make sure JSON is the default format.
+ auto str2 = std::format("{}", *obj);
+ REQUIRE(str == str2);
+}
+
+TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
+ auto str = std::format("{:J}", *obj);
+
+ REQUIRE(str == "{\"int\":[1,42,666]}");
+}
+
+TEST_CASE("ucl: emit configuration with std::format", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
+ auto str = std::format("{:c}", *obj);
+
+ REQUIRE(str ==
+"int [\n"
+" 1,\n"
+" 42,\n"
+" 666,\n"
+"]\n");
+}
+
+TEST_CASE("ucl: emit YAML with std::format", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
+ auto str = std::format("{:y}", *obj);
+
+ REQUIRE(str ==
+"int: [\n"
+" 1,\n"
+" 42,\n"
+" 666\n"
+"]");
+}
diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/tests/integer.cc
new file mode 100644
index 0000000..6584764
--- /dev/null
+++ b/nihil.ucl/tests/integer.cc
@@ -0,0 +1,247 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <cstdint>
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: integer: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ static_assert(std::same_as<std::int64_t, integer::contained_type>);
+ REQUIRE(integer::ucl_type == object_type::integer);
+ REQUIRE(static_cast<::ucl_type>(integer::ucl_type) == UCL_INT);
+
+ static_assert(std::destructible<integer>);
+ static_assert(std::default_initializable<integer>);
+ static_assert(std::move_constructible<integer>);
+ static_assert(std::copy_constructible<integer>);
+ static_assert(std::equality_comparable<integer>);
+ static_assert(std::totally_ordered<integer>);
+ static_assert(std::swappable<integer>);
+}
+
+TEST_CASE("ucl: integer: constructor", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default") {
+ auto i = integer();
+ REQUIRE(i == 0);
+ }
+
+ SECTION("with value") {
+ auto i = integer(42);
+ REQUIRE(i == 42);
+ }
+}
+
+TEST_CASE("ucl: integer: literal", "[ucl]")
+{
+ SECTION("with namespace nihil::ucl::literals") {
+ using namespace nihil::ucl::literals;
+
+ auto i = 42_ucl;
+ REQUIRE(i.type() == nihil::ucl::object_type::integer);
+ REQUIRE(i == 42);
+ }
+
+ SECTION("with namespace nihil::literals") {
+ using namespace nihil::literals;
+
+ auto i = 42_ucl;
+ REQUIRE(i.type() == nihil::ucl::object_type::integer);
+ REQUIRE(i == 42);
+ }
+}
+
+TEST_CASE("ucl: integer: construct from UCL object", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("ref, correct type") {
+ auto uobj = ::ucl_object_fromint(42);
+
+ auto i = integer(ref, uobj);
+ REQUIRE(i == 42);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, correct type") {
+ auto uobj = ::ucl_object_fromint(42);
+
+ auto i = integer(noref, uobj);
+ REQUIRE(i == 42);
+ }
+
+ SECTION("ref, wrong type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, wrong type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+}
+
+TEST_CASE("ucl: integer: make_integer", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default value") {
+ auto i = make_integer().value();
+ REQUIRE(i == 0);
+ }
+
+ SECTION("explicit value") {
+ auto i = make_integer(42).value();
+ REQUIRE(i == 42);
+ }
+}
+
+TEST_CASE("ucl: integer: swap", "[ucl]")
+{
+ // do not add using namespace nihil::ucl
+
+ auto i1 = nihil::ucl::integer(1);
+ auto i2 = nihil::ucl::integer(2);
+
+ swap(i1, i2);
+
+ REQUIRE(i1 == 2);
+ REQUIRE(i2 == 1);
+}
+
+TEST_CASE("ucl: integer: value()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto i = 42_ucl;
+ REQUIRE(i.value() == 42);
+}
+
+TEST_CASE("ucl: integer: key()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("parsed with key") {
+ auto obj = parse("an_int = 42").value();
+ auto i = object_cast<integer>(obj["an_int"]).value();
+ REQUIRE(i.key() == "an_int");
+ }
+
+ SECTION("bare integer, no key") {
+ auto i = 42_ucl;
+ REQUIRE(i.key() == "");
+ }
+}
+
+TEST_CASE("ucl: integer: comparison", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto i = 42_ucl;
+
+ SECTION("operator==") {
+ REQUIRE(i == 42);
+ REQUIRE(i == 42_ucl);
+ }
+
+ SECTION("operator!=") {
+ REQUIRE(i != 1);
+ REQUIRE(i != 1_ucl);
+ }
+
+ SECTION("operator<") {
+ REQUIRE(i < 43);
+ REQUIRE(i < 43_ucl);
+ }
+
+ SECTION("operator>") {
+ REQUIRE(i > 1);
+ REQUIRE(i > 1_ucl);
+ }
+}
+
+TEST_CASE("ucl: integer: parse", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto obj = parse("value = 42").value();
+
+ auto v = obj["value"];
+ REQUIRE(v.key() == "value");
+ REQUIRE(object_cast<integer>(v) == 42);
+}
+
+TEST_CASE("ucl: integer: parse and emit", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto ucl = parse("int = 42;").value();
+
+ auto output = std::string();
+ emit(ucl, emitter::configuration, std::back_inserter(output));
+
+ REQUIRE(output == "int = 42;\n");
+}
+
+TEST_CASE("ucl: integer: format", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare integer") {
+ auto str = std::format("{}", 42_ucl);
+ REQUIRE(str == "42");
+ }
+
+ SECTION("parsed integer") {
+ auto obj = parse("int = 42;").value();
+ auto i = object_cast<integer>(obj["int"]).value();
+
+ auto str = std::format("{}", i);
+ REQUIRE(str == "42");
+ }
+
+ SECTION("with format string") {
+ auto str = std::format("{:-05}", 42_ucl);
+ REQUIRE(str == "00042");
+ }
+}
+
+TEST_CASE("ucl: integer: print to ostream", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare integer") {
+ auto strm = std::ostringstream();
+ strm << 42_ucl;
+
+ REQUIRE(strm.str() == "42");
+ }
+
+ SECTION("parsed integer") {
+ auto obj = parse("int = 42;").value();
+ auto i = object_cast<integer>(obj["int"]).value();
+
+ auto strm = std::ostringstream();
+ strm << i;
+
+ REQUIRE(strm.str() == "42");
+ }
+}
diff --git a/nihil.ucl/tests/map.cc b/nihil.ucl/tests/map.cc
new file mode 100644
index 0000000..7240cb3
--- /dev/null
+++ b/nihil.ucl/tests/map.cc
@@ -0,0 +1,192 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+
+#include <catch2/catch_test_macros.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+//NOLINTBEGIN(bugprone-unchecked-optional-access)
+
+TEST_CASE("ucl: map: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ REQUIRE(map<>::ucl_type == object_type::object);
+ REQUIRE(static_cast<::ucl_type>(map<>::ucl_type) == UCL_OBJECT);
+
+ static_assert(std::destructible<map<>>);
+ static_assert(std::default_initializable<map<>>);
+ static_assert(std::move_constructible<map<>>);
+ static_assert(std::copy_constructible<map<>>);
+ static_assert(std::equality_comparable<map<>>);
+ static_assert(std::totally_ordered<map<>>);
+ static_assert(std::swappable<map<>>);
+
+ static_assert(std::ranges::range<map<integer>>);
+ static_assert(std::same_as<std::pair<std::string_view, integer>,
+ std::ranges::range_value_t<map<integer>>>);
+}
+
+TEST_CASE("ucl: map: default construct", "[ucl]")
+{
+ auto map = nihil::ucl::map<>();
+ REQUIRE(str(map.type()) == "object");
+}
+
+TEST_CASE("ucl: map: construct from initializer_list", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto map = nihil::ucl::map<integer>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ REQUIRE(str(map.type()) == "object");
+ REQUIRE(map["1"] == 1);
+ REQUIRE(map["42"] == 42);
+}
+
+TEST_CASE("ucl: map: construct from range", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto vec = std::vector<std::pair<std::string_view, integer>>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ auto map = nihil::ucl::map<integer>(std::from_range, vec);
+
+ REQUIRE(str(map.type()) == "object");
+ REQUIRE(map["1"] == 1);
+ REQUIRE(map["42"] == 42);
+}
+
+TEST_CASE("ucl: map: construct from iterator pair", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto vec = std::vector<std::pair<std::string_view, integer>>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ auto map = nihil::ucl::map<integer>(std::ranges::begin(vec),
+ std::ranges::end(vec));
+
+ REQUIRE(str(map.type()) == "object");
+ REQUIRE(map["1"] == 1);
+ REQUIRE(map["42"] == 42);
+}
+
+TEST_CASE("ucl: map: insert", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto m = map<integer>();
+
+ m.insert({"test1"sv, integer(42)});
+ m.insert({"test2"sv, integer(666)});
+
+ REQUIRE(m["test1"] == 42);
+ REQUIRE(m["test2"] == 666);
+}
+
+TEST_CASE("ucl: map: find", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto map = nihil::ucl::map<integer>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ auto obj = map.find("42");
+ REQUIRE(obj.value() == 42);
+
+ obj = map.find("43");
+ REQUIRE(!obj.has_value());
+}
+
+TEST_CASE("ucl: map: iterate", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto map = nihil::ucl::map<integer>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ auto i = 0u;
+
+ for (auto [key, value] : map) {
+ if (key == "1")
+ REQUIRE(value == 1);
+ else if (key == "42")
+ REQUIRE(value == 42);
+ else
+ REQUIRE(false);
+ ++i;
+ }
+
+ REQUIRE(i == 2);
+}
+
+TEST_CASE("ucl: map: operator[] throws key_not_found", "[ucl]")
+{
+ auto map = nihil::ucl::map<nihil::ucl::integer>();
+ REQUIRE_THROWS_AS(map["nonesuch"], nihil::ucl::key_not_found);
+}
+
+TEST_CASE("ucl: map: remove", "[uc]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto map = nihil::ucl::map<integer>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ REQUIRE(map.find("42") != std::nullopt);
+ REQUIRE(map.remove("42") == true);
+ REQUIRE(map.find("42") == std::nullopt);
+ REQUIRE(map["1"] == 1);
+
+ REQUIRE(map.remove("42") == false);
+}
+
+TEST_CASE("ucl: map: pop", "[uc]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto map = nihil::ucl::map<integer>{
+ {"1"sv, integer(1)},
+ {"42"sv, integer(42)},
+ };
+
+ REQUIRE(map.find("42") != std::nullopt);
+
+ auto obj = map.pop("42");
+ REQUIRE(obj.value() == 42);
+
+ REQUIRE(!map.find("42"));
+ REQUIRE(map["1"] == 1);
+
+ obj = map.pop("42");
+ REQUIRE(!obj);
+}
+
+//NOLINTEND(bugprone-unchecked-optional-access)
diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/tests/object.cc
new file mode 100644
index 0000000..3ad180e
--- /dev/null
+++ b/nihil.ucl/tests/object.cc
@@ -0,0 +1,44 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl object: get_ucl_object", "[ucl]")
+{
+ auto obj = nihil::ucl::integer(42);
+
+ REQUIRE(obj.get_ucl_object() != nullptr);
+ static_assert(std::same_as<::ucl_object_t *,
+ decltype(obj.get_ucl_object())>);
+
+ auto const cobj = obj;
+ static_assert(std::same_as<::ucl_object_t const *,
+ decltype(cobj.get_ucl_object())>);
+}
+
+TEST_CASE("ucl object: compare", "[ucl]")
+{
+ using namespace std::literals;
+
+ auto obj_41 = nihil::ucl::parse("int = 41;"sv);
+ REQUIRE(obj_41);
+
+ auto obj_42 = nihil::ucl::parse("int = 42;"sv);
+ REQUIRE(obj_42);
+
+ auto obj_42_2 = nihil::ucl::parse("int = 42;"sv);
+ REQUIRE(obj_42_2);
+
+ auto obj_43 = nihil::ucl::parse("int = 43;"sv);
+ REQUIRE(obj_43);
+
+ REQUIRE(*obj_42 == *obj_42_2);
+ REQUIRE(*obj_42 != *obj_43);
+ REQUIRE(*obj_42 < *obj_43);
+ REQUIRE(*obj_42 > *obj_41);
+}
diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/tests/parse.cc
new file mode 100644
index 0000000..43ce219
--- /dev/null
+++ b/nihil.ucl/tests/parse.cc
@@ -0,0 +1,55 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_floating_point.hpp>
+
+import nihil.ucl;
+
+TEST_CASE("ucl parse: iterate array", "[ucl]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto err = parse("value = [1, 42, 666];"sv);
+ REQUIRE(err);
+
+ auto obj = *err;
+
+ auto arr = obj["value"];
+ REQUIRE(arr.key() == "value");
+
+ auto ints = object_cast<array<integer>>(arr);
+ REQUIRE(ints);
+
+ auto vec = std::vector(std::from_range, *ints);
+
+ REQUIRE(vec.size() == 3);
+ REQUIRE(vec[0] == 1);
+ REQUIRE(vec[1] == 42);
+ REQUIRE(vec[2] == 666);
+}
+
+TEST_CASE("ucl parse: iterate hash", "[ucl]")
+{
+ using namespace std::literals;
+ using namespace nihil::ucl;
+
+ auto input = "int = 42; bool = true; str = \"test\";"sv;
+ auto obj = parse(input);
+ REQUIRE(obj);
+
+ for (auto &&[key, value] : *obj) {
+ REQUIRE(key == value.key());
+
+ if (key == "int")
+ REQUIRE(object_cast<integer>(value) == 42);
+ else if (key == "bool")
+ REQUIRE(object_cast<boolean>(value) == true);
+ else if (key == "str")
+ REQUIRE(object_cast<string>(value) == "test");
+ }
+}
diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/tests/real.cc
new file mode 100644
index 0000000..421917e
--- /dev/null
+++ b/nihil.ucl/tests/real.cc
@@ -0,0 +1,248 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_floating_point.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: real: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ static_assert(std::same_as<double, real::contained_type>);
+ REQUIRE(real::ucl_type == object_type::real);
+ REQUIRE(static_cast<::ucl_type>(real::ucl_type) == UCL_FLOAT);
+
+ static_assert(std::destructible<real>);
+ static_assert(std::default_initializable<real>);
+ static_assert(std::move_constructible<real>);
+ static_assert(std::copy_constructible<real>);
+ static_assert(std::equality_comparable<real>);
+ static_assert(std::totally_ordered<real>);
+ static_assert(std::swappable<real>);
+}
+
+TEST_CASE("ucl: real: constructor", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default") {
+ auto r = real();
+ REQUIRE(r == 0);
+ }
+
+ SECTION("with value") {
+ auto r = real(42.1);
+ REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1));
+ }
+}
+
+TEST_CASE("ucl: real: literal", "[ucl]")
+{
+ SECTION("with namespace nihil::ucl::literals") {
+ using namespace nihil::ucl::literals;
+
+ auto r = 42.5_ucl;
+ REQUIRE(r.type() == nihil::ucl::object_type::real);
+ REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5));
+ }
+
+ SECTION("with namespace nihil::literals") {
+ using namespace nihil::literals;
+
+ auto r = 42.5_ucl;
+ REQUIRE(r.type() == nihil::ucl::object_type::real);
+ REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5));
+ }
+}
+
+TEST_CASE("ucl: real: construct from UCL object", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("ref, correct type") {
+ auto uobj = ::ucl_object_fromdouble(42);
+
+ auto r = real(ref, uobj);
+ REQUIRE(r == 42);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, correct type") {
+ auto uobj = ::ucl_object_fromdouble(42);
+
+ auto r = real(noref, uobj);
+ REQUIRE(r == 42);
+ }
+
+ SECTION("ref, wrong type") {
+ auto uobj = ::ucl_object_fromint(42);
+
+ REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, wrong type") {
+ auto uobj = ::ucl_object_fromint(42);
+
+ REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+}
+
+TEST_CASE("ucl: real: make_real", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("default value") {
+ auto i = make_real().value();
+ REQUIRE(i == 0);
+ }
+
+ SECTION("explicit value") {
+ auto i = make_real(42).value();
+ REQUIRE(i == 42);
+ }
+}
+
+TEST_CASE("ucl: real: swap", "[ucl]")
+{
+ // do not add using namespace nihil::ucl
+
+ auto r1 = nihil::ucl::real(1);
+ auto r2 = nihil::ucl::real(2);
+
+ swap(r1, r2);
+
+ REQUIRE(r1 == 2.);
+ REQUIRE(r2 == 1.);
+}
+
+TEST_CASE("ucl: real: value()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto r = 42.5_ucl;
+ REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5));
+}
+
+TEST_CASE("ucl: real: key()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("parsed with key") {
+ auto obj = parse("a_real = 42.5").value();
+ auto r = object_cast<real>(obj["a_real"]).value();
+ REQUIRE(r.key() == "a_real");
+ }
+
+ SECTION("bare real, no key") {
+ auto i = 42.5_ucl;
+ REQUIRE(i.key() == "");
+ }
+}
+
+TEST_CASE("ucl: real: comparison", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto i = nihil::ucl::real(42.5);
+
+ SECTION("operator==") {
+ REQUIRE(i == 42.5);
+ REQUIRE(i == 42.5_ucl);
+ }
+
+ SECTION("operator!=") {
+ REQUIRE(i != 1);
+ REQUIRE(i != 1._ucl);
+ }
+
+ SECTION("operator<") {
+ REQUIRE(i < 43);
+ REQUIRE(i < 43._ucl);
+ }
+
+ SECTION("operator>") {
+ REQUIRE(i > 1);
+ REQUIRE(i > 1._ucl);
+ }
+}
+
+TEST_CASE("ucl: real: parse", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto obj = parse("value = 42.1").value();
+
+ auto v = obj["value"];
+ REQUIRE(v.key() == "value");
+ REQUIRE_THAT(object_cast<real>(v).value().value(),
+ Catch::Matchers::WithinRel(42.1));
+}
+
+TEST_CASE("ucl: real: parse and emit", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto ucl = parse("real = 42.2").value();
+
+ auto output = std::string();
+ emit(ucl, emitter::configuration, std::back_inserter(output));
+
+ REQUIRE(output == "real = 42.2;\n");
+}
+
+TEST_CASE("ucl: real: format", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare real") {
+ auto str = std::format("{}", 42.5_ucl);
+ REQUIRE(str == "42.5");
+ }
+
+ SECTION("parsed real") {
+ auto obj = parse("real = 42.5;").value();
+ auto r = object_cast<real>(obj["real"]).value();
+
+ auto str = std::format("{}", r);
+ REQUIRE(str == "42.5");
+ }
+
+ SECTION("with format string") {
+ auto str = std::format("{:10.5f}", 42.5_ucl);
+ REQUIRE(str == " 42.50000");
+ }
+}
+
+TEST_CASE("ucl: real: print to ostream", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("bare real") {
+ auto strm = std::ostringstream();
+ strm << 42.5_ucl;
+
+ REQUIRE(strm.str() == "42.5");
+ }
+
+ SECTION("parsed real") {
+ auto obj = parse("real = 42.5;").value();
+ auto i = object_cast<real>(obj["real"]).value();
+
+ auto strm = std::ostringstream();
+ strm << i;
+
+ REQUIRE(strm.str() == "42.5");
+ }
+}
diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/tests/string.cc
new file mode 100644
index 0000000..6409b8d
--- /dev/null
+++ b/nihil.ucl/tests/string.cc
@@ -0,0 +1,415 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <concepts>
+#include <list>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+#include <ucl.h>
+
+import nihil.ucl;
+
+TEST_CASE("ucl: string: invariants", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ static_assert(std::same_as<std::string_view, string::contained_type>);
+ REQUIRE(string::ucl_type == object_type::string);
+ REQUIRE(static_cast<::ucl_type>(string::ucl_type) == UCL_STRING);
+
+ static_assert(std::destructible<string>);
+ static_assert(std::default_initializable<string>);
+ static_assert(std::move_constructible<string>);
+ static_assert(std::copy_constructible<string>);
+ static_assert(std::equality_comparable<string>);
+ static_assert(std::totally_ordered<string>);
+ static_assert(std::swappable<string>);
+
+ static_assert(std::ranges::contiguous_range<string>);
+ static_assert(std::same_as<char, std::ranges::range_value_t<string>>);
+}
+
+TEST_CASE("ucl: string: literal", "[ucl]")
+{
+ SECTION("with namespace nihil::ucl::literals") {
+ using namespace nihil::ucl::literals;
+
+ auto s = "testing"_ucl;
+ REQUIRE(s.type() == nihil::ucl::object_type::string);
+ REQUIRE(s == "testing");
+ }
+
+ SECTION("with namespace nihil::literals") {
+ using namespace nihil::literals;
+
+ auto s = "testing"_ucl;
+ REQUIRE(s.type() == nihil::ucl::object_type::string);
+ REQUIRE(s == "testing");
+ }
+}
+
+TEST_CASE("ucl: string: construct", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ SECTION("empty string") {
+ auto str = string();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "");
+ }
+
+ SECTION("with integer-like value") {
+ auto str = "42"_ucl;
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "42");
+ }
+
+ SECTION("with boolean-like value") {
+ auto str = "true"_ucl;
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "true");
+ }
+
+ SECTION("from string literal") {
+ auto str = string("testing");
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from std::string") {
+ auto str = string("testing"s);
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from std::string_view") {
+ auto str = string("testing"sv);
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from contiguous range") {
+ auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = string(s);
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from non-contiguous range") {
+ auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = string(s);
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from contiguous iterator pair") {
+ auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = string(s.begin(), s.end());
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from non-contiguous iterator pair") {
+ auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = string(s.begin(), s.end());
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+}
+
+TEST_CASE("ucl: string: construct from UCL object", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ SECTION("ref, correct type") {
+ auto uobj = ::ucl_object_fromstring("testing");
+
+ auto s = string(ref, uobj);
+ REQUIRE(s == "testing");
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, correct type") {
+ auto uobj = ::ucl_object_fromstring("testing");
+
+ auto s = string(noref, uobj);
+ REQUIRE(s == "testing");
+ }
+
+ SECTION("ref, wrong type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+
+ SECTION("noref, wrong type") {
+ auto uobj = ::ucl_object_frombool(true);
+
+ REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch);
+
+ ::ucl_object_unref(uobj);
+ }
+}
+
+TEST_CASE("ucl: string: make_string", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ SECTION("empty string") {
+ auto str = make_string().value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "");
+ }
+
+ SECTION("from string literal") {
+ auto str = make_string("testing").value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from std::string") {
+ auto str = make_string("testing"s).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from std::string_view") {
+ auto str = make_string("testing"sv).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from contiguous range") {
+ auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = make_string(s).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from non-contiguous range") {
+ auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = make_string(s).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from contiguous iterator pair") {
+ auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = make_string(s.begin(), s.end()).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("from non-contiguous iterator pair") {
+ auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'};
+ auto str = make_string(s.begin(), s.end()).value();
+ REQUIRE(str.type() == object_type::string);
+ REQUIRE(str == "testing");
+ }
+}
+
+TEST_CASE("ucl: string: swap", "[ucl]")
+{
+ // do not add using namespace nihil::ucl
+
+ auto s1 = nihil::ucl::string("one");
+ auto s2 = nihil::ucl::string("two");
+
+ swap(s1, s2);
+
+ REQUIRE(s1 == "two");
+ REQUIRE(s2 == "one");
+}
+
+TEST_CASE("ucl: string: value()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto s = string("te\"st");
+ REQUIRE(s.value() == "te\"st");
+}
+
+TEST_CASE("ucl: string: key()", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto err = parse("a_string = \"test\"");
+ REQUIRE(err);
+
+ auto obj = *err;
+ REQUIRE(object_cast<string>(obj["a_string"])->key() == "a_string");
+
+ auto s = string("test");
+ REQUIRE(s.key() == "");
+}
+
+TEST_CASE("ucl: string: size", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ REQUIRE(string().size() == 0);
+ REQUIRE(string("test").size() == 4);
+}
+
+TEST_CASE("ucl: string: empty", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ REQUIRE(string().empty() == true);
+ REQUIRE(string("test").empty() == false);
+}
+
+TEST_CASE("ucl: string: iterate", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto str = "test"_ucl;
+
+ SECTION("as iterator pair") {
+ auto begin = str.begin();
+ static_assert(std::contiguous_iterator<decltype(begin)>);
+
+ auto end = str.end();
+ static_assert(std::sentinel_for<decltype(end),
+ decltype(begin)>);
+
+ REQUIRE(*begin == 't');
+ ++begin;
+ REQUIRE(*begin == 'e');
+ ++begin;
+ REQUIRE(*begin == 's');
+ ++begin;
+ REQUIRE(*begin == 't');
+ ++begin;
+
+ REQUIRE(begin == end);
+ }
+
+ SECTION("as range") {
+ auto s = std::string(std::from_range, str);
+ REQUIRE(s == "test");
+ }
+}
+
+TEST_CASE("ucl: string: comparison", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto str = "testing"_ucl;
+
+ SECTION("operator==") {
+ REQUIRE(str == "testing"_ucl);
+ REQUIRE(str == std::string_view("testing"));
+ REQUIRE(str == std::string("testing"));
+ REQUIRE(str == "testing");
+ }
+
+ SECTION("operator!=") {
+ REQUIRE(str != "test"_ucl);
+ REQUIRE(str != std::string_view("test"));
+ REQUIRE(str != std::string("test"));
+ REQUIRE(str != "test");
+ }
+
+ SECTION("operator<") {
+ REQUIRE(str < "zzz"_ucl);
+ REQUIRE(str < std::string_view("zzz"));
+ REQUIRE(str < std::string("zzz"));
+ REQUIRE(str < "zzz");
+ }
+
+ SECTION("operator>") {
+ REQUIRE(str > "aaa"_ucl);
+ REQUIRE(str > std::string_view("aaa"));
+ REQUIRE(str > std::string("aaa"));
+ REQUIRE(str > "aaa");
+ }
+}
+
+TEST_CASE("ucl: string: parse", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto obj = parse("value = \"te\\\"st\"").value();
+
+ auto v = obj["value"];
+ REQUIRE(v.key() == "value");
+ REQUIRE(object_cast<nihil::ucl::string>(v).value() == "te\"st");
+}
+
+TEST_CASE("ucl: string: emit", "[ucl]")
+{
+ using namespace nihil::ucl;
+
+ auto ucl = parse("str = \"te\\\"st\";").value();
+
+ auto output = std::string();
+ emit(ucl, emitter::configuration, std::back_inserter(output));
+
+ REQUIRE(output == "str = \"te\\\"st\";\n");
+}
+
+TEST_CASE("ucl: string: format", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto constexpr test_string = "te\"st"sv;
+
+ SECTION("bare string") {
+ auto str = std::format("{}", string(test_string));
+ REQUIRE(str == test_string);
+ }
+
+ SECTION("parsed string") {
+ auto obj = parse("string = \"te\\\"st\";").value();
+ auto s = object_cast<string>(obj["string"]).value();
+
+ auto str = std::format("{}", s);
+ REQUIRE(str == test_string);
+ }
+
+ SECTION("with format string") {
+ auto str = std::format("{: >10}", string(test_string));
+ REQUIRE(str == " te\"st");
+ }
+}
+
+TEST_CASE("ucl: string: print to ostream", "[ucl]")
+{
+ using namespace nihil::ucl;
+ using namespace std::literals;
+
+ auto constexpr test_string = "te\"st"sv;
+
+ SECTION("bare string") {
+ auto strm = std::ostringstream();
+ strm << string(test_string);
+
+ REQUIRE(strm.str() == test_string);
+ }
+
+ SECTION("parsed string") {
+ auto obj = parse("string = \"te\\\"st\";").value();
+ auto s = object_cast<string>(obj["string"]).value();
+
+ auto strm = std::ostringstream();
+ strm << s;
+
+ REQUIRE(strm.str() == test_string);
+ }
+
+ SECTION("with format string") {
+ auto str = std::format("{: >10}", string(test_string));
+ REQUIRE(str == " te\"st");
+ }
+}
diff --git a/nihil.ucl/type.cc b/nihil.ucl/type.cc
new file mode 100644
index 0000000..7d9cad7
--- /dev/null
+++ b/nihil.ucl/type.cc
@@ -0,0 +1,62 @@
+/*
+ * 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
new file mode 100644
index 0000000..f3b3aef
--- /dev/null
+++ b/nihil.ucl/type.ccm
@@ -0,0 +1,58 @@
+/*
+ * 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.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,
+};
+
+// Get the name of a type.
+export auto str(object_type type) -> std::string_view;
+
+// Concept of a UCL data type.
+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>;
+ { T::ucl_type } -> std::convertible_to<object_type>;
+};
+
+// Exception thrown when a type assertion fails.
+export struct type_mismatch : error {
+ type_mismatch(object_type expected_type, object_type actual_type);
+
+ // The type we expected.
+ auto expected_type(this type_mismatch const &self) -> object_type;
+ // The type we got.
+ auto actual_type(this type_mismatch const &self) -> object_type;
+
+private:
+ object_type m_expected_type;
+ object_type m_actual_type;
+};
+
+} // namespace nihil::ucl
diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt
new file mode 100644
index 0000000..a07ea7d
--- /dev/null
+++ b/nihil.util/CMakeLists.txt
@@ -0,0 +1,38 @@
+# 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_sources(nihil.util
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.util.ccm
+
+ capture_stream.ccm
+ ctype.ccm
+ parse_size.ccm
+ next_word.ccm
+ skipws.ccm
+ tabulate.ccm
+)
+
+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
+ )
+ target_link_libraries(nihil.util.test PRIVATE
+ nihil.util
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.util.test)
+endif()
diff --git a/nihil.util/capture_stream.ccm b/nihil.util/capture_stream.ccm
new file mode 100644
index 0000000..7ec39a9
--- /dev/null
+++ b/nihil.util/capture_stream.ccm
@@ -0,0 +1,62 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <iostream>
+
+export module nihil.util:capture_stream;
+
+namespace nihil {
+
+/*
+ * Capture output written to a stream and redirect it to an internal string
+ * buffer. Call .str() to get the data written. Call .release() to stop
+ * capturing (or simply delete the capture_stream object).
+ */
+export template<typename Char, typename Traits>
+struct capture_stream {
+ capture_stream(std::basic_ostream<Char, Traits> &stream)
+ : m_stream(&stream)
+ {
+ m_old_streambuf = m_stream->rdbuf();
+ m_stream->rdbuf(m_buffer.rdbuf());
+ }
+
+ ~capture_stream()
+ {
+ if (m_old_streambuf == nullptr)
+ return;
+ m_stream->rdbuf(m_old_streambuf);
+ }
+
+ /*
+ * Release this capture, returning the stream to its previous state.
+ */
+ auto release(this capture_stream &self) -> void
+ {
+ if (self.m_old_streambuf == nullptr)
+ throw std::logic_error(
+ "release() called on empty capture_stream");
+
+ self.m_stream->rdbuf(self.m_old_streambuf);
+ self.m_old_streambuf = nullptr;
+ }
+
+ /*
+ * Get the data which has been written to the stream.
+ */
+ [[nodiscard]] auto str(this capture_stream const &self)
+ -> std::basic_string_view<Char, Traits>
+ {
+ return self.m_buffer.view();
+ }
+
+private:
+ std::basic_ostringstream<Char, Traits> m_buffer;
+ std::basic_ostream<Char, Traits> *m_stream;
+ std::streambuf *m_old_streambuf;
+};
+
+} // namespace nihil
diff --git a/nihil.util/ctype.ccm b/nihil.util/ctype.ccm
new file mode 100644
index 0000000..6d30c4f
--- /dev/null
+++ b/nihil.util/ctype.ccm
@@ -0,0 +1,87 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <concepts>
+#include <locale>
+
+export module nihil.util:ctype;
+
+namespace nihil {
+
+/*
+ * ctype_is: wrap std::ctype<T>::is() in a form suitable for use as an algorithm
+ * predicate, i.e., ctype_is(m) will return a functor object that takes any char
+ * type as an argument and returns bool.
+ *
+ * If the locale is not specified, the current global locale is used by default.
+ *
+ * ctype_is copies the locale, so passing a temporary is fine.
+ */
+
+export struct ctype_is final {
+ ctype_is(std::ctype_base::mask mask_,
+ std::locale const &locale_ = std::locale())
+ : m_mask(mask_)
+ , m_locale(locale_)
+ {}
+
+ [[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);
+ return facet.is(self.m_mask, c);
+ }
+
+private:
+ std::ctype_base::mask m_mask;
+ std::locale m_locale;
+};
+
+// Predefined tests for the current global locale.
+
+export inline auto is_space = ctype_is(std::ctype_base::space);
+export inline auto is_print = ctype_is(std::ctype_base::print);
+export inline auto is_cntrl = ctype_is(std::ctype_base::cntrl);
+export inline auto is_upper = ctype_is(std::ctype_base::upper);
+export inline auto is_lower = ctype_is(std::ctype_base::lower);
+export inline auto is_alpha = ctype_is(std::ctype_base::alpha);
+export inline auto is_digit = ctype_is(std::ctype_base::digit);
+export inline auto is_punct = ctype_is(std::ctype_base::punct);
+export inline auto is_xdigit = ctype_is(std::ctype_base::xdigit);
+export inline auto is_blank = ctype_is(std::ctype_base::blank);
+export inline auto is_alnum = ctype_is(std::ctype_base::alnum);
+export inline auto is_graph = ctype_is(std::ctype_base::graph);
+
+// Predefined tests for the C locale. The C locale is guaranteed to always be
+// available, so this doesn't create lifetime issues.
+
+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());
+
+} // namespace nihil
diff --git a/nihil.util/next_word.ccm b/nihil.util/next_word.ccm
new file mode 100644
index 0000000..c5d3ad7
--- /dev/null
+++ b/nihil.util/next_word.ccm
@@ -0,0 +1,49 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <algorithm>
+#include <locale>
+#include <ranges>
+#include <string>
+#include <utility>
+
+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>>
+{
+ text = skipws(text, locale);
+
+ auto is_space = ctype_is(std::ctype_base::space, locale);
+ auto split_pos = std::ranges::find_if(text, is_space);
+
+ return {{std::ranges::begin(text), split_pos},
+ {split_pos, std::ranges::end(text)}};
+}
+
+export template<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);
+ *text = rest;
+ return word;
+}
+
+} // namespace nihil
diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm
new file mode 100644
index 0000000..89510c9
--- /dev/null
+++ b/nihil.util/nihil.util.ccm
@@ -0,0 +1,14 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil.util;
+
+export import :capture_stream;
+export import :ctype;
+export import :parse_size;
+export import :next_word;
+export import :skipws;
+export import :tabulate;
diff --git a/nihil.util/parse_size.ccm b/nihil.util/parse_size.ccm
new file mode 100644
index 0000000..c95ac50
--- /dev/null
+++ b/nihil.util/parse_size.ccm
@@ -0,0 +1,107 @@
+/*
+ * 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>
+
+export module nihil.util:parse_size;
+
+import nihil.core;
+import nihil.error;
+import nihil.monad;
+
+import :ctype;
+
+namespace nihil {
+
+template<typename Char>
+auto get_multiplier(Char c) -> std::expected<std::uint64_t, error>
+{
+ auto ret = std::uint64_t{1};
+
+ switch (c) {
+ case 'p': case 'P': ret *= 1024; //NOLINT
+ case 't': case 'T': ret *= 1024; //NOLINT
+ case 'g': case 'G': ret *= 1024; //NOLINT
+ case 'm': case 'M': ret *= 1024; //NOLINT
+ case 'k': case 'K': ret *= 1024; //NOLINT
+ return ret;
+
+ default:
+ return std::unexpected(error(errc::invalid_unit));
+ }
+}
+
+/*
+ * 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);
+
+ if (num_str.empty())
+ co_return std::unexpected(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));
+ 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));
+ ret += digit;
+ }
+
+ if (it == str.end())
+ // No multiplier.
+ co_return ret;
+
+ auto mchar = *it++;
+
+ if (it != str.end())
+ // Multiplier is more than one character.
+ co_return std::unexpected(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 ret * mult;
+}
+
+export template<typename T>
+[[nodiscard]] inline 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)
+{
+ return parse_size<T>(std::wstring_view(s));
+}
+
+}
diff --git a/nihil.util/skipws.ccm b/nihil.util/skipws.ccm
new file mode 100644
index 0000000..4813ae8
--- /dev/null
+++ b/nihil.util/skipws.ccm
@@ -0,0 +1,40 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <algorithm>
+#include <locale>
+#include <ranges>
+#include <string>
+
+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())
+ -> std::basic_string_view<Char>
+{
+ auto is_space = ctype_is(std::ctype_base::space, locale);
+ auto nonws = std::ranges::find_if_not(text, is_space);
+ return {nonws, std::ranges::end(text)};
+}
+
+export template<typename Char>
+auto skipws(std::basic_string_view<Char> *text,
+ std::locale const &locale = std::locale())
+ -> void
+{
+ *text = skipws(*text, locale);
+}
+
+} // namespace nihil
diff --git a/nihil.util/tabulate.ccm b/nihil.util/tabulate.ccm
new file mode 100644
index 0000000..5998b24
--- /dev/null
+++ b/nihil.util/tabulate.ccm
@@ -0,0 +1,312 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <algorithm>
+#include <cstdlib>
+#include <format>
+#include <ranges>
+#include <iterator>
+#include <vector>
+
+export module nihil.util:tabulate;
+
+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
+ */
+
+// Exception thrown when a table spec is invalid.
+export struct table_spec_error : error {
+ table_spec_error(std::string_view what)
+ : error(what)
+ {
+ }
+};
+
+/*
+ * The specification for a single field.
+ */
+template<typename Char>
+struct field_spec {
+ enum align_t { left, right };
+
+ // Get the name of this field.
+ auto name(this field_spec const &self)
+ -> std::basic_string_view<Char>
+ {
+ return self.m_name;
+ }
+
+ // Set the name of this field.
+ auto name(this field_spec &self,
+ std::basic_string_view<Char> new_name)
+ -> void
+ {
+ self.m_name = new_name;
+ }
+
+ // Set this field's alignment.
+ auto align(this field_spec &self, align_t new_align) -> void
+ {
+ self.m_align = new_align;
+ }
+
+ // Ensure the length of this field is at least the given width.
+ auto ensure_width(this field_spec &self, std::size_t newwidth)
+ -> void
+ {
+ self.m_width = std::max(self.m_width, newwidth);
+ }
+
+ // Format an object to a string based on our field spec.
+ [[nodiscard]] auto format(this field_spec const &, auto &&obj)
+ -> std::basic_string<Char>
+ {
+ auto format_string = std::basic_string<Char>{'{', '}'};
+ return std::format(std::runtime_format(format_string), obj);
+ }
+
+ // Print a column value to an output iterator according to our field
+ // spec. If is_last is true, this is the last field on the line, so
+ // we won't output any trailling padding.
+ auto print(this field_spec const &self,
+ std::basic_string_view<Char> value,
+ std::output_iterator<Char> auto &out,
+ bool is_last)
+ -> void
+ {
+ auto padding = self.m_width - value.size();
+
+ if (self.m_align == right)
+ for (std::size_t i = 0; i < padding; ++i)
+ *out++ = ' ';
+
+ std::ranges::copy(value, out);
+
+ if (!is_last && self.m_align == left)
+ for (std::size_t i = 0; i < padding; ++i)
+ *out++ = ' ';
+ }
+
+private:
+ std::basic_string_view<Char> m_name;
+ std::size_t m_width = 0;
+ align_t m_align = left;
+};
+
+/*
+ * The specification for an entire table.
+ */
+template<typename Char>
+struct table_spec {
+ // Add a new field spec to this table.
+ auto add(this table_spec &self, field_spec<Char> field) -> void
+ {
+ self.m_fields.emplace_back(std::move(field));
+ }
+
+ // Return the field spec for a given field. If the field doesn't
+ // exist, this field and any intermediate fields will be created.
+ [[nodiscard]] auto field(this table_spec &self, std::size_t fieldnr)
+ -> field_spec<Char> &
+ {
+ if (fieldnr >= self.m_fields.size())
+ self.m_fields.resize(fieldnr + 1);
+ return self.m_fields.at(fieldnr);
+ }
+
+ // The number of columns in this table.
+ [[nodiscard]] auto columns(this table_spec const &self) -> std::size_t
+ {
+ return self.m_fields.size();
+ }
+
+ // Return all the fields in this table.
+ [[nodiscard]] auto fields(this table_spec const &self)
+ -> std::vector<field_spec<Char>> const &
+ {
+ return self.m_fields;
+ }
+
+private:
+ std::vector<field_spec<Char>> m_fields;
+};
+
+// Parse the field flags, e.g. '<'.
+template<typename Char,
+ std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
+auto parse_field_flags(field_spec<Char> &field, Iterator &pos, Sentinel end)
+ -> void
+{
+ while (pos < end) {
+ switch (*pos) {
+ case '<':
+ field.align(field_spec<Char>::left);
+ break;
+ case '>':
+ field.align(field_spec<Char>::right);
+ break;
+ case ':':
+ ++pos;
+ /*FALLTHROUGH*/
+ case '}':
+ return;
+ default:
+ throw table_spec_error("Invalid table spec: "
+ "unknown flag character");
+ }
+
+ if (++pos == end)
+ throw table_spec_error("Invalid table spec: "
+ "unterminated field");
+ }
+}
+
+// Parse a complete field spec, e.g. "{<:NAME}".
+template<typename Char,
+ std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
+[[nodiscard]] auto parse_field(Iterator &pos, Sentinel end)
+ -> field_spec<Char>
+{
+ auto field = field_spec<Char>{};
+
+ if (pos == end)
+ throw table_spec_error("Invalid table spec: empty field");
+
+ // The field spec should start with a '{'.
+ if (*pos != '{')
+ throw table_spec_error("Invalid table spec: expected '{'");
+
+ if (++pos == end)
+ throw table_spec_error("Invalid table spec: unterminated field");
+
+ // This consumes 'pos' up to and including the ':'.
+ parse_field_flags(field, pos, end);
+
+ auto brace = std::ranges::find(pos, end, '}');
+ if (brace == end)
+ throw table_spec_error("Invalid table spec: expected '}'");
+
+ field.name(std::basic_string_view<Char>(pos, brace));
+ pos = std::next(brace);
+
+ // The field must be at least as wide as its header.
+ field.ensure_width(field.name().size());
+
+ return field;
+}
+
+template<typename Char>
+[[nodiscard]] auto parse_table_spec(std::basic_string_view<Char> spec)
+ -> table_spec<Char>
+{
+ auto table = table_spec<Char>();
+
+ auto pos = std::ranges::begin(spec);
+ auto end = std::ranges::end(spec);
+
+ for (;;) {
+ // Skip leading whitespace
+ while (pos < end && is_c_space(*pos))
+ ++pos;
+
+ if (pos == end)
+ break;
+
+ table.add(parse_field<Char>(pos, end));
+ }
+
+ return table;
+}
+
+export template<typename Char,
+ std::ranges::range Range,
+ std::output_iterator<Char> Iterator>
+auto basic_tabulate(std::basic_string_view<Char> table_spec,
+ Range &&range,
+ Iterator &&out)
+ -> void
+{
+ // Parse the table spec.
+ auto table = parse_table_spec(table_spec);
+
+ // Create our copy of the input data.
+ auto data = std::vector<std::vector<std::basic_string<Char>>>();
+ // Reserve the first row for the header.
+ data.resize(1);
+
+ // Find the required length of each field.
+ for (auto &&row : range) {
+ // LLVM doesn't have std::enumerate_view yet
+ auto i = std::size_t{0};
+ auto &this_row = data.emplace_back();
+
+ for (auto &&column : row) {
+ auto &field = table.field(i);
+ auto &str = this_row.emplace_back(field.format(column));
+ field.ensure_width(str.size());
+ ++i;
+ }
+ }
+
+ // Add the header row.
+ for (auto &&field : table.fields())
+ data.at(0).emplace_back(std::from_range, field.name());
+
+ // Print the values.
+ for (auto &&row : data) {
+ for (std::size_t i = 0; i < row.size(); ++i) {
+ auto &field = table.field(i);
+ bool is_last = (i == row.size() - 1);
+
+ field.print(row[i], out, is_last);
+
+ if (!is_last)
+ *out++ = ' ';
+ }
+
+ *out++ = '\n';
+ }
+}
+
+export auto tabulate(std::string_view table_spec,
+ std::ranges::range auto &&range,
+ std::output_iterator<char> auto &&out)
+{
+ return basic_tabulate<char>(table_spec,
+ std::forward<decltype(range)>(range),
+ std::forward<decltype(out)>(out));
+}
+
+export auto wtabulate(std::wstring_view table_spec,
+ std::ranges::range auto &&range,
+ std::output_iterator<wchar_t> auto &&out)
+{
+ return basic_tabulate<wchar_t>(table_spec,
+ std::forward<decltype(range)>(range),
+ std::forward<decltype(out)>(out));
+}
+
+} // namespace nihil
diff --git a/nihil.util/test_capture_stream.cc b/nihil.util/test_capture_stream.cc
new file mode 100644
index 0000000..27c8596
--- /dev/null
+++ b/nihil.util/test_capture_stream.cc
@@ -0,0 +1,44 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <iostream>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.util;
+
+TEST_CASE("nihil.util: capture", "[nihil][nihil.util]")
+{
+ SECTION("std::cout with release()") {
+ auto cap = nihil::capture_stream(std::cout);
+
+ std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n';
+ REQUIRE(cap.str() == "1+1=2\n");
+
+ cap.release();
+ REQUIRE(cap.str() == "1+1=2\n");
+ }
+
+ SECTION("std::cout with dtor") {
+ auto cap = nihil::capture_stream(std::cout);
+ std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n';
+ REQUIRE(cap.str() == "1+1=2\n");
+ }
+
+ SECTION("std::cerr with release()") {
+ auto cap = nihil::capture_stream(std::cerr);
+
+ std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n';
+ REQUIRE(cap.str() == "1+1=2\n");
+
+ cap.release();
+ REQUIRE(cap.str() == "1+1=2\n");
+ }
+
+ SECTION("std::cerr with dtor") {
+ auto cap = nihil::capture_stream(std::cerr);
+ std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n';
+ REQUIRE(cap.str() == "1+1=2\n");
+ }
+}
diff --git a/nihil.util/test_ctype.cc b/nihil.util/test_ctype.cc
new file mode 100644
index 0000000..62721d1
--- /dev/null
+++ b/nihil.util/test_ctype.cc
@@ -0,0 +1,373 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.util;
+
+TEST_CASE("ctype: space", "[ctype]") {
+ auto is_utf8_space =
+ nihil::ctype_is(std::ctype_base::space,
+ std::locale("C.UTF-8"));
+
+ // '\v' (vertical tab) is a space
+ REQUIRE(nihil::is_space('\v') == true);
+ REQUIRE(nihil::is_space(L'\v') == true);
+
+ REQUIRE(nihil::is_c_space('\v') == true);
+ REQUIRE(nihil::is_c_space(L'\v') == true);
+
+ REQUIRE(is_utf8_space('\v') == true);
+ REQUIRE(is_utf8_space(L'\v') == true);
+
+ // 'x' is not a space
+ REQUIRE(nihil::is_space('x') == false);
+ REQUIRE(nihil::is_space(L'x') == false);
+
+ REQUIRE(nihil::is_c_space('x') == false);
+ REQUIRE(nihil::is_c_space(L'x') == false);
+
+ REQUIRE(is_utf8_space('x') == false);
+ REQUIRE(is_utf8_space(L'x') == false);
+
+ // U+2003 EM SPACE is a space
+ REQUIRE(nihil::is_space(L'\u2003') == false);
+ REQUIRE(nihil::is_c_space(L'\u2003') == false);
+ REQUIRE(is_utf8_space(L'\u2003') == true);
+}
+
+TEST_CASE("ctype: print", "[ctype]") {
+ auto is_utf8_print =
+ nihil::ctype_is(std::ctype_base::print,
+ std::locale("C.UTF-8"));
+
+ // 'x' is printable
+ REQUIRE(nihil::is_print('x') == true);
+ REQUIRE(nihil::is_print(L'x') == true);
+
+ REQUIRE(nihil::is_c_print('x') == true);
+ REQUIRE(nihil::is_c_print(L'x') == true);
+
+ REQUIRE(is_utf8_print('x') == true);
+ REQUIRE(is_utf8_print(L'x') == true);
+
+ // '\003' is not printable
+ REQUIRE(nihil::is_print('\003') == false);
+ REQUIRE(nihil::is_print(L'\003') == false);
+
+ REQUIRE(nihil::is_c_print('\003') == false);
+ REQUIRE(nihil::is_c_print(L'\003') == false);
+
+ REQUIRE(is_utf8_print('\003') == false);
+ REQUIRE(is_utf8_print(L'\003') == false);
+
+ // U+0410 CYRILLIC CAPITAL LETTER A is printable
+ REQUIRE(nihil::is_print(L'\u0410') == false);
+ REQUIRE(nihil::is_c_print(L'\u0410') == false);
+ REQUIRE(is_utf8_print(L'\u0410') == true);
+}
+
+TEST_CASE("ctype: cntrl", "[ctype]") {
+ auto is_utf8_cntrl =
+ nihil::ctype_is(std::ctype_base::cntrl,
+ std::locale("C.UTF-8"));
+
+ // '\003' is a control character
+ REQUIRE(nihil::is_cntrl('\003') == true);
+ REQUIRE(nihil::is_cntrl(L'\003') == true);
+
+ REQUIRE(nihil::is_c_cntrl('\003') == true);
+ REQUIRE(nihil::is_c_cntrl(L'\003') == true);
+
+ REQUIRE(is_utf8_cntrl('\003') == true);
+ REQUIRE(is_utf8_cntrl(L'\003') == true);
+
+
+ // 'x' is not a control character
+ REQUIRE(nihil::is_cntrl('x') == false);
+ REQUIRE(nihil::is_cntrl(L'x') == false);
+
+ REQUIRE(nihil::is_c_cntrl('x') == false);
+ REQUIRE(nihil::is_c_cntrl(L'x') == false);
+
+ REQUIRE(is_utf8_cntrl('x') == false);
+ REQUIRE(is_utf8_cntrl(L'x') == false);
+
+ // U+00AD SOFT HYPHEN is a control character.
+ REQUIRE(nihil::is_cntrl(L'\u00ad') == false);
+ REQUIRE(nihil::is_c_cntrl(L'\u00ad') == false);
+ REQUIRE(is_utf8_cntrl(L'\u00ad') == true);
+}
+
+TEST_CASE("ctype: upper", "[ctype]") {
+ auto is_utf8_upper =
+ nihil::ctype_is(std::ctype_base::upper,
+ std::locale("C.UTF-8"));
+
+ // 'A' is upper case
+ REQUIRE(nihil::is_upper('A') == true);
+ REQUIRE(nihil::is_upper(L'A') == true);
+
+ REQUIRE(nihil::is_c_upper('A') == true);
+ REQUIRE(nihil::is_c_upper(L'A') == true);
+
+ REQUIRE(is_utf8_upper('A') == true);
+ REQUIRE(is_utf8_upper(L'A') == true);
+
+ // 'a' is not upper case
+ REQUIRE(nihil::is_upper('a') == false);
+ REQUIRE(nihil::is_upper(L'a') == false);
+
+ REQUIRE(nihil::is_c_upper('a') == false);
+ REQUIRE(nihil::is_c_upper(L'a') == false);
+
+ REQUIRE(is_utf8_upper('a') == false);
+ REQUIRE(is_utf8_upper(L'a') == false);
+
+ // U+0410 CYRILLIC CAPITAL LETTER A is upper case
+ REQUIRE(nihil::is_upper(L'\u0410') == false);
+ REQUIRE(nihil::is_c_upper(L'\u0410') == false);
+ REQUIRE(is_utf8_upper(L'\u0410') == true);
+}
+
+TEST_CASE("ctype: lower", "[ctype]") {
+ auto is_utf8_lower =
+ nihil::ctype_is(std::ctype_base::lower,
+ std::locale("C.UTF-8"));
+
+ // 'a' is lower case
+ REQUIRE(nihil::is_lower('a') == true);
+ REQUIRE(nihil::is_lower(L'a') == true);
+
+ REQUIRE(nihil::is_c_lower('a') == true);
+ REQUIRE(nihil::is_c_lower(L'a') == true);
+
+ REQUIRE(is_utf8_lower('a') == true);
+ REQUIRE(is_utf8_lower(L'a') == true);
+
+ // 'A' is not lower case
+ REQUIRE(nihil::is_lower('A') == false);
+ REQUIRE(nihil::is_lower(L'A') == false);
+
+ REQUIRE(nihil::is_c_lower('A') == false);
+ REQUIRE(nihil::is_c_lower(L'A') == false);
+
+ REQUIRE(is_utf8_lower('A') == false);
+ REQUIRE(is_utf8_lower(L'A') == false);
+
+ // U+0430 CYRILLIC SMALL LETTER A
+ REQUIRE(nihil::is_lower(L'\u0430') == false);
+ REQUIRE(nihil::is_c_lower(L'\u0430') == false);
+ REQUIRE(is_utf8_lower(L'\u0430') == true);
+}
+
+TEST_CASE("ctype: alpha", "[ctype]") {
+ auto is_utf8_alpha =
+ nihil::ctype_is(std::ctype_base::alpha,
+ std::locale("C.UTF-8"));
+
+ // 'a' is alphabetical
+ REQUIRE(nihil::is_alpha('a') == true);
+ REQUIRE(nihil::is_alpha(L'a') == true);
+
+ REQUIRE(nihil::is_c_alpha('a') == true);
+ REQUIRE(nihil::is_c_alpha(L'a') == true);
+
+ REQUIRE(is_utf8_alpha('a') == true);
+ REQUIRE(is_utf8_alpha(L'a') == true);
+
+ // '1' is not alphabetical
+ REQUIRE(nihil::is_alpha('1') == false);
+ REQUIRE(nihil::is_alpha(L'1') == false);
+
+ REQUIRE(nihil::is_c_alpha('1') == false);
+ REQUIRE(nihil::is_c_alpha(L'1') == false);
+
+ REQUIRE(is_utf8_alpha('1') == false);
+ REQUIRE(is_utf8_alpha(L'1') == false);
+
+ // U+0430 CYRILLIC SMALL LETTER A
+ REQUIRE(nihil::is_alpha(L'\u0430') == false);
+ REQUIRE(nihil::is_c_alpha(L'\u0430') == false);
+ REQUIRE(is_utf8_alpha(L'\u0430') == true);
+}
+
+TEST_CASE("ctype: digit", "[ctype]") {
+ auto is_utf8_digit =
+ nihil::ctype_is(std::ctype_base::digit,
+ std::locale("C.UTF-8"));
+
+ // '1' is a digit
+ REQUIRE(nihil::is_digit('1') == true);
+ REQUIRE(nihil::is_digit(L'1') == true);
+
+ REQUIRE(nihil::is_c_digit('1') == true);
+ REQUIRE(nihil::is_c_digit(L'1') == true);
+
+ REQUIRE(is_utf8_digit('1') == true);
+ REQUIRE(is_utf8_digit(L'1') == true);
+
+ // 'a' is not a digit
+ REQUIRE(nihil::is_digit('a') == false);
+ REQUIRE(nihil::is_digit(L'a') == false);
+
+ REQUIRE(nihil::is_c_digit('a') == false);
+ REQUIRE(nihil::is_c_digit(L'a') == false);
+
+ REQUIRE(is_utf8_digit('a') == false);
+ REQUIRE(is_utf8_digit(L'a') == false);
+
+ // U+0660 ARABIC-INDIC DIGIT ZERO
+ REQUIRE(nihil::is_digit(L'\u0660') == false);
+ REQUIRE(nihil::is_c_digit(L'\u0660') == false);
+ REQUIRE(is_utf8_digit(L'\u0660') == true);
+}
+
+TEST_CASE("ctype: punct", "[ctype]") {
+ auto is_utf8_punct =
+ nihil::ctype_is(std::ctype_base::punct,
+ std::locale("C.UTF-8"));
+
+ // ';' is punctuation
+ REQUIRE(nihil::is_punct(';') == true);
+ REQUIRE(nihil::is_punct(L';') == true);
+
+ REQUIRE(nihil::is_c_punct(';') == true);
+ REQUIRE(nihil::is_c_punct(L';') == true);
+
+ REQUIRE(is_utf8_punct(';') == true);
+ REQUIRE(is_utf8_punct(L';') == true);
+
+ // 'a' is not punctuation
+ REQUIRE(nihil::is_punct('a') == false);
+ REQUIRE(nihil::is_punct(L'a') == false);
+
+ REQUIRE(nihil::is_c_punct('a') == false);
+ REQUIRE(nihil::is_c_punct(L'a') == false);
+
+ REQUIRE(is_utf8_punct('a') == false);
+ REQUIRE(is_utf8_punct(L'a') == false);
+
+ // U+00A1 INVERTED EXCLAMATION MARK
+ REQUIRE(nihil::is_punct(L'\u00A1') == false);
+ REQUIRE(nihil::is_c_punct(L'\u00A1') == false);
+ REQUIRE(is_utf8_punct(L'\u00A1') == true);
+}
+
+TEST_CASE("ctype: xdigit", "[ctype]") {
+ auto is_utf8_xdigit =
+ nihil::ctype_is(std::ctype_base::xdigit,
+ std::locale("C.UTF-8"));
+
+ // 'f' is an xdigit
+ REQUIRE(nihil::is_xdigit('f') == true);
+ REQUIRE(nihil::is_xdigit(L'f') == true);
+
+ REQUIRE(nihil::is_c_xdigit('f') == true);
+ REQUIRE(nihil::is_c_xdigit(L'f') == true);
+
+ REQUIRE(is_utf8_xdigit('f') == true);
+ REQUIRE(is_utf8_xdigit(L'f') == true);
+
+ // 'g' is not an xdigit
+ REQUIRE(nihil::is_xdigit('g') == false);
+ REQUIRE(nihil::is_xdigit(L'g') == false);
+
+ REQUIRE(nihil::is_c_xdigit('g') == false);
+ REQUIRE(nihil::is_c_xdigit(L'g') == false);
+
+ REQUIRE(is_utf8_xdigit('g') == false);
+ REQUIRE(is_utf8_xdigit(L'g') == false);
+}
+
+TEST_CASE("ctype: blank", "[ctype]") {
+ auto is_utf8_blank =
+ nihil::ctype_is(std::ctype_base::blank,
+ std::locale("C.UTF-8"));
+
+ // '\t' is a blank
+ REQUIRE(nihil::is_blank('\t') == true);
+ REQUIRE(nihil::is_blank(L'\t') == true);
+
+ REQUIRE(nihil::is_c_blank('\t') == true);
+ REQUIRE(nihil::is_c_blank(L'\t') == true);
+
+ REQUIRE(is_utf8_blank('\t') == true);
+ REQUIRE(is_utf8_blank(L'\t') == true);
+
+ // '\v' is not a blank
+ REQUIRE(nihil::is_blank('\v') == false);
+ REQUIRE(nihil::is_blank(L'\v') == false);
+
+ REQUIRE(nihil::is_c_blank('\v') == false);
+ REQUIRE(nihil::is_c_blank(L'\v') == false);
+
+ REQUIRE(is_utf8_blank('\v') == false);
+ REQUIRE(is_utf8_blank(L'\v') == false);
+
+ // There don't seem to be any UTF-8 blank characters, at least
+ // in FreeBSD libc.
+}
+
+TEST_CASE("ctype: alnum", "[ctype]") {
+ auto is_utf8_alnum =
+ nihil::ctype_is(std::ctype_base::alnum,
+ std::locale("C.UTF-8"));
+
+ // 'a' is alphanumeric
+ REQUIRE(nihil::is_alnum('a') == true);
+ REQUIRE(nihil::is_alnum(L'a') == true);
+
+ REQUIRE(nihil::is_c_alnum('a') == true);
+ REQUIRE(nihil::is_c_alnum(L'a') == true);
+
+ REQUIRE(is_utf8_alnum('a') == true);
+ REQUIRE(is_utf8_alnum(L'a') == true);
+
+ // '\t' is not a alnum
+ REQUIRE(nihil::is_alnum('\t') == false);
+ REQUIRE(nihil::is_alnum(L'\t') == false);
+
+ REQUIRE(nihil::is_c_alnum('\t') == false);
+ REQUIRE(nihil::is_c_alnum(L'\t') == false);
+
+ REQUIRE(is_utf8_alnum('\t') == false);
+ REQUIRE(is_utf8_alnum(L'\t') == false);
+
+ // U+0430 CYRILLIC SMALL LETTER A
+ REQUIRE(nihil::is_alnum(L'\u0430') == false);
+ REQUIRE(nihil::is_c_alnum(L'\u0430') == false);
+ REQUIRE(is_utf8_alnum(L'\u0430') == true);
+}
+
+TEST_CASE("ctype: graph", "[ctype]") {
+ auto is_utf8_graph =
+ nihil::ctype_is(std::ctype_base::graph,
+ std::locale("C.UTF-8"));
+
+ // 'a' is graphical
+ REQUIRE(nihil::is_graph('a') == true);
+ REQUIRE(nihil::is_graph(L'a') == true);
+
+ REQUIRE(nihil::is_c_graph('a') == true);
+ REQUIRE(nihil::is_c_graph(L'a') == true);
+
+ REQUIRE(is_utf8_graph('a') == true);
+ REQUIRE(is_utf8_graph(L'a') == true);
+
+ // '\t' is not graphical
+ REQUIRE(nihil::is_graph('\t') == false);
+ REQUIRE(nihil::is_graph(L'\t') == false);
+
+ REQUIRE(nihil::is_c_graph('\t') == false);
+ REQUIRE(nihil::is_c_graph(L'\t') == false);
+
+ REQUIRE(is_utf8_graph('\t') == false);
+ REQUIRE(is_utf8_graph(L'\t') == false);
+
+ // U+0430 CYRILLIC SMALL LETTER A
+ REQUIRE(nihil::is_graph(L'\u0430') == false);
+ REQUIRE(nihil::is_c_graph(L'\u0430') == false);
+ REQUIRE(is_utf8_graph(L'\u0430') == true);
+}
diff --git a/nihil.util/test_next_word.cc b/nihil.util/test_next_word.cc
new file mode 100644
index 0000000..7e61237
--- /dev/null
+++ b/nihil.util/test_next_word.cc
@@ -0,0 +1,65 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <locale>
+#include <string>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.util;
+
+TEST_CASE("next_word: basic", "[next_word]")
+{
+ using namespace std::literals;
+ auto s = "foo bar baz"sv;
+
+ auto words = nihil::next_word(s);
+ REQUIRE(words.first == "foo");
+ REQUIRE(words.second == " bar baz");
+
+ auto word = nihil::next_word(&s);
+ REQUIRE(word == "foo");
+ REQUIRE(s == " bar baz");
+}
+
+TEST_CASE("next_word: multiple spaces", "[next_word]")
+{
+ using namespace std::literals;
+ auto s = "foo bar baz"sv;
+
+ auto words = nihil::next_word(s);
+ REQUIRE(words.first == "foo");
+ REQUIRE(words.second == " bar baz");
+
+ auto word = nihil::next_word(&s);
+ REQUIRE(word == "foo");
+ REQUIRE(s == " bar baz");
+}
+
+TEST_CASE("next_word: leading spaces", "[next_word]")
+{
+ using namespace std::literals;
+ auto s = " \tfoo bar baz"sv;
+
+ auto words = nihil::next_word(s);
+ REQUIRE(words.first == "foo");
+ REQUIRE(words.second == " bar baz");
+
+ auto word = nihil::next_word(&s);
+ REQUIRE(word == "foo");
+ REQUIRE(s == " bar baz");
+}
+
+TEST_CASE("next_word: locale", "[next_word]")
+{
+ using namespace std::literals;
+ auto s = L"\u2003foo\u2003bar\u2003baz"sv;
+
+ auto words = nihil::next_word(s);
+ REQUIRE(words.first == s);
+
+ words = nihil::next_word(s, std::locale("C.UTF-8"));
+ REQUIRE(words.first == L"foo");
+ REQUIRE(words.second == L"\u2003bar\u2003baz");
+}
diff --git a/nihil.util/test_parse_size.cc b/nihil.util/test_parse_size.cc
new file mode 100644
index 0000000..692039b
--- /dev/null
+++ b/nihil.util/test_parse_size.cc
@@ -0,0 +1,169 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <cstdint>
+#include <system_error>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.core;
+import nihil.error;
+import nihil.util;
+
+TEST_CASE("parse_size: empty value", "[nihil]")
+{
+ using namespace nihil;
+
+ auto n = parse_size<std::uint64_t>("");
+ REQUIRE(!n);
+ REQUIRE(n.error() == nihil::errc::empty_string);
+}
+
+TEST_CASE("parse_size: basic", "[nihil]")
+{
+ using namespace nihil;
+
+ SECTION("bare number") {
+ auto n = parse_size<std::uint64_t>("1024").value();
+ REQUIRE(n == 1024);
+ }
+
+ SECTION("max value, unsigned") {
+ auto n = parse_size<std::uint16_t>("65535").value();
+ REQUIRE(n == 65535);
+ }
+
+ SECTION("max value, signed") {
+ auto n = parse_size<std::uint16_t>("32767").value();
+ REQUIRE(n == 32767);
+ }
+
+ SECTION("overflow by 1, unsigned") {
+ auto n = parse_size<std::uint16_t>("65536");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+
+ SECTION("overflow by 1, signed") {
+ auto n = parse_size<std::int16_t>("32768");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+
+ SECTION("overflow by many, unsigned") {
+ auto n = parse_size<std::uint16_t>("100000");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+
+ SECTION("overflow by many, signed") {
+ auto n = parse_size<std::int16_t>("100000");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+}
+
+TEST_CASE("parse_size: invalid multiplier", "[nihil]")
+{
+ using namespace nihil;
+
+ auto n = parse_size<std::uint64_t>("4z");
+ REQUIRE(!n);
+ REQUIRE(n.error() == nihil::errc::invalid_unit);
+
+ n = parse_size<std::uint64_t>("4kz");
+ REQUIRE(!n);
+ REQUIRE(n.error() == nihil::errc::invalid_unit);
+}
+
+TEST_CASE("parse_size: multipliers", "[nihil]")
+{
+ using namespace nihil;
+
+ auto sf = static_cast<std::uint64_t>(4);
+
+ SECTION("k") {
+ auto n = parse_size<std::uint64_t>("4k").value();
+ REQUIRE(n == sf * 1024);
+ }
+
+ SECTION("m") {
+ auto n = parse_size<std::uint64_t>("4m").value();
+ REQUIRE(n == sf * 1024 * 1024);
+ }
+
+ SECTION("g") {
+ auto n = parse_size<std::uint64_t>("4g").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024);
+ }
+
+ SECTION("t") {
+ auto n = parse_size<std::uint64_t>("4t").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024);
+ }
+
+ SECTION("p") {
+ auto n = parse_size<std::uint64_t>("4p").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024);
+ }
+}
+
+TEST_CASE("parse_size: multiplier overflow", "[nihil]")
+{
+ using namespace nihil;
+
+ SECTION("signed") {
+ auto n = parse_size<std::uint16_t>("64k");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+
+ SECTION("unsigned") {
+ auto n = parse_size<std::int16_t>("32k");
+ REQUIRE(!n);
+ REQUIRE(n.error() == std::errc::result_out_of_range);
+ }
+}
+
+TEST_CASE("parse_size: wide", "[nihil]")
+{
+ using namespace nihil;
+
+ SECTION("bare number") {
+ auto n = parse_size<std::uint64_t>(L"1024").value();
+ REQUIRE(n == 1024);
+ }
+}
+
+TEST_CASE("parse_size: wide multipliers", "[nihil]")
+{
+ using namespace nihil;
+
+ auto sf = static_cast<std::uint64_t>(4);
+
+ SECTION("k") {
+ auto n = parse_size<std::uint64_t>(L"4k").value();
+ REQUIRE(n == sf * 1024);
+ }
+
+ SECTION("m") {
+ auto n = parse_size<std::uint64_t>(L"4m").value();
+ REQUIRE(n == sf * 1024 * 1024);
+ }
+
+ SECTION("g") {
+ auto n = parse_size<std::uint64_t>(L"4g").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024);
+ }
+
+ SECTION("t") {
+ auto n = parse_size<std::uint64_t>(L"4t").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024);
+ }
+
+ SECTION("p") {
+ auto n = parse_size<std::uint64_t>(L"4p").value();
+ REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024);
+ }
+}
diff --git a/nihil.util/test_skipws.cc b/nihil.util/test_skipws.cc
new file mode 100644
index 0000000..837c1f3
--- /dev/null
+++ b/nihil.util/test_skipws.cc
@@ -0,0 +1,45 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <locale>
+#include <string>
+using namespace std::literals;
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.util;
+
+TEST_CASE("skipws: basic", "[skipws]")
+{
+ REQUIRE(nihil::skipws("foo"sv) == "foo");
+ REQUIRE(nihil::skipws(" foo"sv) == "foo");
+ REQUIRE(nihil::skipws("foo "sv) == "foo ");
+ REQUIRE(nihil::skipws("foo bar"sv) == "foo bar");
+}
+
+TEST_CASE("skipws: pointer", "[skipws]")
+{
+ auto s = "foo"sv;
+ nihil::skipws(&s);
+ REQUIRE(s == "foo");
+
+ s = " foo"sv;
+ nihil::skipws(&s);
+ REQUIRE(s == "foo");
+
+ s = "foo "sv;
+ nihil::skipws(&s);
+ REQUIRE(s == "foo ");
+
+ s = "foo bar"sv;
+ nihil::skipws(&s);
+ REQUIRE(s == "foo bar");
+}
+
+TEST_CASE("skipws: locale", "[skipws]")
+{
+ // Assume the default locale is C.
+ REQUIRE(nihil::skipws(L"\u2003foo"sv) == L"\u2003foo");
+ REQUIRE(nihil::skipws(L"\u2003foo"sv, std::locale("C.UTF-8")) == L"foo");
+}
diff --git a/nihil.util/test_tabulate.cc b/nihil.util/test_tabulate.cc
new file mode 100644
index 0000000..8dee796
--- /dev/null
+++ b/nihil.util/test_tabulate.cc
@@ -0,0 +1,75 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.util;
+
+using namespace std::literals;
+using namespace nihil;
+
+TEST_CASE("tabulate: basic", "[tabulate]")
+{
+ auto input = std::vector{
+ std::vector{"a", "foo", "b"},
+ std::vector{"bar", "c", "baz"},
+ };
+
+ auto result = std::string();
+ tabulate("{:1} {:2} {:3}", input, std::back_inserter(result));
+ REQUIRE(result ==
+"1 2 3\n"
+"a foo b\n"
+"bar c baz\n");
+}
+
+TEST_CASE("tabulate: basic wide", "[tabulate]")
+{
+ auto input = std::vector{
+ std::vector{L"a", L"foo", L"b"},
+ std::vector{L"bar", L"c", L"baz"},
+ };
+
+ auto result = std::wstring();
+ wtabulate(L"{:1} {:2} {:3}", input, std::back_inserter(result));
+
+ REQUIRE(result ==
+L"1 2 3\n"
+"a foo b\n"
+"bar c baz\n");
+}
+
+TEST_CASE("tabulate: jagged", "[tabulate]")
+{
+ auto input = std::vector{
+ std::vector{"a", "foo", "b"},
+ std::vector{"bar", "baz"},
+ };
+
+ auto result = std::string();
+ tabulate("{:1} {:2} {:3}", input, std::back_inserter(result));
+ REQUIRE(result ==
+"1 2 3\n"
+"a foo b\n"
+"bar baz\n");
+}
+
+TEST_CASE("tabulate: align", "[tabulate]")
+{
+ auto input = std::vector{
+ std::vector{"a", "longvalue", "s"},
+ std::vector{"a", "s", "longvalue"},
+ };
+
+ auto result = std::string();
+ tabulate("{:1} {<:2} {>:3}", input, std::back_inserter(result));
+ REQUIRE(result ==
+"1 2 3\n"
+"a longvalue s\n"
+"a s longvalue\n");
+}
diff --git a/nihil.uuid/CMakeLists.txt b/nihil.uuid/CMakeLists.txt
new file mode 100644
index 0000000..fe037e7
--- /dev/null
+++ b/nihil.uuid/CMakeLists.txt
@@ -0,0 +1,23 @@
+# This source code is released into the public domain.
+
+add_library(nihil.uuid STATIC)
+target_sources(nihil.uuid
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ uuid.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.uuid.test test.cc)
+ target_link_libraries(nihil.uuid.test PRIVATE
+ nihil.uuid
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.uuid.test)
+endif()
diff --git a/nihil.uuid/test.cc b/nihil.uuid/test.cc
new file mode 100644
index 0000000..0f21298
--- /dev/null
+++ b/nihil.uuid/test.cc
@@ -0,0 +1,1001 @@
+/*
+ * From https://github.com/mariusbancila/stduuid
+ *
+ * Copyright (c) 2017
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <algorithm>
+#include <random>
+#include <set>
+#include <unordered_set>
+
+#include <catch2/catch_test_macros.hpp>
+
+//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)
+{
+ using engine_type = typename EngineT::result_type;
+ using device_type = std::random_device::result_type;
+ using seedseq_type = std::seed_seq::result_type;
+
+ constexpr auto bytes_needed = StateSize * sizeof(engine_type);
+ constexpr auto numbers_needed =
+ (sizeof(device_type) < sizeof(seedseq_type))
+ ? (bytes_needed / sizeof(device_type))
+ : (bytes_needed / sizeof(seedseq_type));
+
+ auto numbers = std::array<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));
+ engine.seed(seedseq);
+}
+
+} // anonymous namespace
+
+import nihil.uuid;
+
+using namespace nihil;
+
+TEST_CASE("uuid: Test multiple default generators", "[uuid]")
+{
+ uuid id1;
+ uuid id2;
+
+ {
+ std::random_device rd;
+ 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);
+
+ id1 = uuid_random_generator{ generator }();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+ }
+
+ {
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ id2 = uuid_random_generator{ generator }();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+ }
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test default generator", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ uuid const guid = uuid_random_generator{generator}();
+ REQUIRE(!guid.is_nil());
+ REQUIRE(guid.version() == uuid_version::random_number_based);
+ REQUIRE(guid.variant() == uuid_variant::rfc);
+}
+
+TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)",
+ "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ uuid_random_generator dgen(&generator);
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ uuid_random_generator dgen(generator.get());
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ uuid_random_generator dgen(generator);
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) "
+ "w/ ranlux48_base", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<int, 6> {};
+ std::ranges::generate(seed_data, std::ref(rd));
+ std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
+ std::ranlux48_base generator(seq);
+
+ basic_uuid_random_generator<std::ranlux48_base> dgen(&generator);
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) "
+ "w/ ranlux48_base", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<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);
+
+ basic_uuid_random_generator<std::ranlux48_base> dgen(generator.get());
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ref) "
+ "w/ ranlux48_base", "[uuid]")
+{
+ std::random_device rd;
+ auto seed_data = std::array<int, 6> {};
+ std::ranges::generate(seed_data, std::ref(rd));
+ std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
+ std::ranlux48_base generator(seq);
+
+ basic_uuid_random_generator<std::ranlux48_base> dgen(generator);
+ auto id1 = dgen();
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::random_number_based);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen();
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::random_number_based);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+}
+
+TEST_CASE("uuid: Test namespaces", "[uuid]")
+{
+ REQUIRE(uuid_namespace_dns == uuid::from_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8"));
+ REQUIRE(uuid_namespace_url == uuid::from_string("6ba7b811-9dad-11d1-80b4-00c04fd430c8"));
+ REQUIRE(uuid_namespace_oid == uuid::from_string("6ba7b812-9dad-11d1-80b4-00c04fd430c8"));
+ REQUIRE(uuid_namespace_x500 == uuid::from_string("6ba7b814-9dad-11d1-80b4-00c04fd430c8"));
+}
+
+TEST_CASE("uuid: Test name generator (char*)", "[uuid]")
+{
+ uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value());
+
+ auto id1 = dgen("john");
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::name_based_sha1);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen("jane");
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::name_based_sha1);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ auto id3 = dgen("jane");
+ REQUIRE(!id3.is_nil());
+ REQUIRE(id3.version() == uuid_version::name_based_sha1);
+ REQUIRE(id3.variant() == uuid_variant::rfc);
+
+ auto id4 = dgen(L"jane");
+ REQUIRE(!id4.is_nil());
+ REQUIRE(id4.version() == uuid_version::name_based_sha1);
+ REQUIRE(id4.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+ REQUIRE(id2 == id3);
+ REQUIRE(id3 != id4);
+}
+
+TEST_CASE("uuid: Test name generator (std::string)", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value());
+ auto id1 = dgen("john"s);
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::name_based_sha1);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen("jane"s);
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::name_based_sha1);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ auto id3 = dgen("jane"s);
+ REQUIRE(!id3.is_nil());
+ REQUIRE(id3.version() == uuid_version::name_based_sha1);
+ REQUIRE(id3.variant() == uuid_variant::rfc);
+
+ auto id4 = dgen(L"jane"s);
+ REQUIRE(!id4.is_nil());
+ REQUIRE(id4.version() == uuid_version::name_based_sha1);
+ REQUIRE(id4.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+ REQUIRE(id2 == id3);
+ REQUIRE(id3 != id4);
+}
+
+TEST_CASE("uuid: Test name generator (std::string_view)", "[uuid]")
+{
+ using namespace std::string_view_literals;
+
+ uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value());
+ auto id1 = dgen("john"sv);
+ REQUIRE(!id1.is_nil());
+ REQUIRE(id1.version() == uuid_version::name_based_sha1);
+ REQUIRE(id1.variant() == uuid_variant::rfc);
+
+ auto id2 = dgen("jane"sv);
+ REQUIRE(!id2.is_nil());
+ REQUIRE(id2.version() == uuid_version::name_based_sha1);
+ REQUIRE(id2.variant() == uuid_variant::rfc);
+
+ auto id3 = dgen("jane"sv);
+ REQUIRE(!id3.is_nil());
+ REQUIRE(id3.version() == uuid_version::name_based_sha1);
+ REQUIRE(id3.variant() == uuid_variant::rfc);
+
+ auto id4 = dgen(L"jane"sv);
+ REQUIRE(!id4.is_nil());
+ REQUIRE(id4.version() == uuid_version::name_based_sha1);
+ REQUIRE(id4.variant() == uuid_variant::rfc);
+
+ REQUIRE(id1 != id2);
+ REQUIRE(id2 == id3);
+ REQUIRE(id3 != id4);
+}
+
+TEST_CASE("uuid: Test name generator equality (char const*, std::string, "
+ "std::string_view)", "[uuid]")
+{
+ using namespace std::literals;
+
+ auto dgen = uuid_name_generator(uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e43").value());
+ auto id1 = dgen("john");
+ auto id2 = dgen("john"s);
+ auto id3 = dgen("john"sv);
+
+ REQUIRE(id1 == id2);
+ REQUIRE(id2 == id3);
+}
+
+TEST_CASE("uuid: Test default constructor", "[uuid]")
+{
+ auto empty = uuid();
+ REQUIRE(empty.is_nil());
+}
+
+TEST_CASE("uuid: Test string conversion", "[uuid]")
+{
+ auto empty = uuid();
+ REQUIRE(to_string(empty) ==
+ "00000000-0000-0000-0000-000000000000");
+ REQUIRE(to_string<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}"));
+}
+
+TEST_CASE("uuid: Test is_valid_uuid(basic_string)", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = L"{47183823-2574-4bfd-b411-99ed177d3e43}"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "00000000-0000-0000-0000-000000000000"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "{00000000-0000-0000-0000-000000000000}"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = L"00000000-0000-0000-0000-000000000000"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = L"{00000000-0000-0000-0000-000000000000}"s;
+ REQUIRE(uuid::is_valid_uuid(str));
+ }
+}
+
+TEST_CASE("uuid: Test is_valid_uuid(basic_string_view)", "[uuid]")
+{
+ using namespace std::string_view_literals;
+
+ REQUIRE(uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e43"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ "{47183823-2574-4bfd-b411-99ed177d3e43}"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ L"47183823-2574-4bfd-b411-99ed177d3e43"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ L"{47183823-2574-4bfd-b411-99ed177d3e43}"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ "00000000-0000-0000-0000-000000000000"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ "{00000000-0000-0000-0000-000000000000}"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ L"00000000-0000-0000-0000-000000000000"sv));
+ REQUIRE(uuid::is_valid_uuid(
+ L"{00000000-0000-0000-0000-000000000000}"sv));
+}
+
+TEST_CASE("uuid: Test is_valid_uuid(char*) invalid format", "[uuid]")
+{
+ REQUIRE(!uuid::is_valid_uuid(""));
+ REQUIRE(!uuid::is_valid_uuid("{}"));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e4"));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e430"));
+ REQUIRE(!uuid::is_valid_uuid(
+ "{47183823-2574-4bfd-b411-99ed177d3e43"));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e43}"));
+}
+
+TEST_CASE("uuid: Test is_valid_uuid(basic_string) invalid format", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ {
+ auto str = ""s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "{}"s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s;
+ REQUIRE(!uuid::is_valid_uuid(str));
+ }
+}
+
+TEST_CASE("uuid: Test is_valid_uuid(basic_string_view) invalid format",
+ "[uuid]")
+{
+ using namespace std::string_view_literals;
+
+ REQUIRE(!uuid::is_valid_uuid(""sv));
+ REQUIRE(!uuid::is_valid_uuid("{}"sv));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e4"sv));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e430"sv));
+ REQUIRE(!uuid::is_valid_uuid(
+ "{47183823-2574-4bfd-b411-99ed177d3e43"sv));
+ REQUIRE(!uuid::is_valid_uuid(
+ "47183823-2574-4bfd-b411-99ed177d3e43}"sv));
+}
+
+TEST_CASE("uuid: Test from_string(char*)", "[uuid]")
+{
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == str);
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto str = L"47183823-2574-4bfd-b411-99ed177d3e43";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string<wchar_t>(guid) == str);
+ }
+
+ {
+ auto str = "4718382325744bfdb41199ed177d3e43";
+ REQUIRE_NOTHROW(uuid::from_string(str));
+ REQUIRE(uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "00000000-0000-0000-0000-000000000000";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = "{00000000-0000-0000-0000-000000000000}";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"00000000-0000-0000-0000-000000000000";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"{00000000-0000-0000-0000-000000000000}";
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+}
+
+TEST_CASE("uuid: Test from_string(basic_string)", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == str);
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"s).value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string<wchar_t>(guid) == str);
+ }
+
+ {
+ auto str = "4718382325744bfdb41199ed177d3e43"s;
+ REQUIRE_NOTHROW(uuid::from_string(str));
+ REQUIRE(uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "00000000-0000-0000-0000-000000000000"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = "{00000000-0000-0000-0000-000000000000}"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"00000000-0000-0000-0000-000000000000"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"{00000000-0000-0000-0000-000000000000}"s;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+}
+
+TEST_CASE("uuid: Test from_string(basic_string_view)", "[uuid]")
+{
+ using namespace std::string_view_literals;
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == str);
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"sv).value();
+ REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43");
+ REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43");
+ }
+
+ {
+ auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(to_string<wchar_t>(guid) == str);
+ }
+
+ {
+ auto str = "4718382325744bfdb41199ed177d3e43"sv;
+ REQUIRE_NOTHROW(uuid::from_string(str));
+ REQUIRE(uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "00000000-0000-0000-0000-000000000000"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = "{00000000-0000-0000-0000-000000000000}"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"00000000-0000-0000-0000-000000000000"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+
+ {
+ auto str = L"{00000000-0000-0000-0000-000000000000}"sv;
+ auto guid = uuid::from_string(str).value();
+ REQUIRE(guid.is_nil());
+ }
+}
+
+TEST_CASE("uuid: Test constexpr from_string", "[uuid]")
+{
+ constexpr uuid value = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value();
+ static_assert(!value.is_nil());
+ static_assert(value.variant() == uuid_variant::rfc);
+ static_assert(value.version() != uuid_version::none);
+}
+
+TEST_CASE("uuid: Test from_string(char*) invalid format", "[uuid]")
+{
+ REQUIRE(!uuid::from_string("").has_value());
+ REQUIRE(!uuid::from_string("{}").has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e4").has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e430").has_value());
+ REQUIRE(!uuid::from_string(
+ "{47183823-2574-4bfd-b411-99ed177d3e43").has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e43}").has_value());
+}
+
+TEST_CASE("uuid: Test from_string(basic_string) invalid format", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ {
+ auto str = ""s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "{}"s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+
+ {
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s;
+ REQUIRE(!uuid::from_string(str).has_value());
+ }
+}
+
+TEST_CASE("uuid: Test from_string(basic_string_view) invalid format", "[uuid]")
+{
+ using namespace std::string_view_literals;
+
+ REQUIRE(!uuid::from_string(""sv).has_value());
+ REQUIRE(!uuid::from_string("{}"sv).has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e4"sv).has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e430"sv).has_value());
+ REQUIRE(!uuid::from_string(
+ "{47183823-2574-4bfd-b411-99ed177d3e43"sv).has_value());
+ REQUIRE(!uuid::from_string(
+ "47183823-2574-4bfd-b411-99ed177d3e43}"sv).has_value());
+}
+
+TEST_CASE("uuid: Test iterators constructor", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ {
+ std::array<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);
+ }
+
+ {
+ 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);
+ }
+}
+
+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
+ }};
+
+ 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
+ }};
+
+ uuid guid(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 guid(arr);
+ REQUIRE(to_string(guid) ==
+ "47183823-2574-4bfd-b411-99ed177d3e43"s);
+ }
+}
+
+TEST_CASE("uuid: Test equality", "[uuid]")
+{
+ uuid empty;
+
+ auto engine = uuid_random_generator::engine_type{};
+ seed_rng(engine);
+ uuid guid = uuid_random_generator{engine}();
+
+ REQUIRE(empty == empty);
+ REQUIRE(guid == guid);
+ REQUIRE(empty != guid);
+}
+
+TEST_CASE("Test comparison", "[uuid]")
+{
+ auto empty = uuid{};
+
+ auto engine = uuid_random_generator::engine_type{};
+ seed_rng(engine);
+
+ uuid_random_generator gen{engine};
+ auto id = gen();
+
+ REQUIRE(empty < id);
+
+ std::set<uuid> ids{
+ uuid{},
+ gen(),
+ gen(),
+ gen(),
+ gen()
+ };
+
+ REQUIRE(ids.size() == 5);
+ REQUIRE(ids.find(uuid{}) != ids.end());
+}
+
+TEST_CASE("uuid: Test hashing", "[uuid]")
+{
+ using namespace std::string_literals;
+
+ auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s;
+ auto guid = uuid::from_string(str).value();
+
+ auto h1 = std::hash<std::string>{};
+ auto h2 = std::hash<uuid>{};
+ REQUIRE(h1(str) != h2(guid));
+
+ 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()
+ };
+
+ REQUIRE(ids.size() == 5);
+ REQUIRE(ids.find(uuid{}) != ids.end());
+}
+
+TEST_CASE("uuid: Test swap", "[uuid]")
+{
+ uuid empty;
+
+ auto engine = uuid_random_generator::engine_type{};
+ seed_rng(engine);
+ uuid guid = uuid_random_generator{engine}();
+
+ REQUIRE(empty.is_nil());
+ REQUIRE(!guid.is_nil());
+
+ std::swap(empty, guid);
+
+ REQUIRE(!empty.is_nil());
+ REQUIRE(guid.is_nil());
+
+ empty.swap(guid);
+
+ REQUIRE(empty.is_nil());
+ REQUIRE(!guid.is_nil());
+}
+
+TEST_CASE("uuid: Test constexpr", "[uuid]")
+{
+ constexpr uuid empty;
+ static_assert(empty.is_nil());
+ static_assert(empty.variant() == uuid_variant::ncs);
+ static_assert(empty.version() == uuid_version::none);
+}
+
+TEST_CASE("uuid: Test size", "[uuid]")
+{
+ REQUIRE(sizeof(uuid) == 16);
+}
+
+TEST_CASE("uuid: Test assignment", "[uuid]")
+{
+ auto id1 = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value();
+ auto id2 = id1;
+ REQUIRE(id1 == id2);
+
+ id1 = uuid::from_string("{fea43102-064f-4444-adc2-02cec42623f8}").value();
+ REQUIRE(id1 != id2);
+
+ auto id3 = std::move(id2);
+ REQUIRE(to_string(id3) == "47183823-2574-4bfd-b411-99ed177d3e43");
+}
+
+TEST_CASE("uuid: Test trivial", "[uuid]")
+{
+ REQUIRE(std::is_trivially_copyable_v<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
+ }};
+
+ {
+ uuid id{ arr };
+ REQUIRE(!id.is_nil());
+
+ auto view = id.as_bytes();
+ REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0);
+ }
+
+ {
+ const uuid id{ arr };
+ REQUIRE(!id.is_nil());
+
+ auto view = id.as_bytes();
+ REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0);
+ }
+}
+
+//NOLINTEND(bugprone-unchecked-optional-access)
diff --git a/nihil.uuid/uuid.ccm b/nihil.uuid/uuid.ccm
new file mode 100644
index 0000000..4aa424e
--- /dev/null
+++ b/nihil.uuid/uuid.ccm
@@ -0,0 +1,842 @@
+/*
+ * 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>
+
+export module nihil.uuid;
+
+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'));
+
+ if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f'))
+ 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 0;
+}
+
+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'));
+}
+
+template <typename TChar>
+[[nodiscard]] constexpr auto to_string_view(TChar const *str) noexcept
+ -> std::basic_string_view<TChar>
+{
+ if (str)
+ return str;
+ return {};
+}
+
+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>
+{
+ return str;
+}
+
+struct sha1
+{
+ using digest32_t = std::array<std::uint32_t, 5>;
+ using digest8_t = std::array<std::uint8_t, 20>;
+
+ static constexpr unsigned int block_bytes = 64;
+
+ sha1()
+ {
+ reset();
+ }
+
+ [[nodiscard]] inline static auto
+ left_rotate(std::uint32_t value, std::size_t const count) noexcept
+ -> std::uint32_t
+ {
+ return (value << count) ^ (value >> (32 - count));
+ }
+
+ auto reset(this sha1 &self) noexcept -> void
+ {
+ self.m_digest[0] = 0x67452301;
+ self.m_digest[1] = 0xEFCDAB89;
+ self.m_digest[2] = 0x98BADCFE;
+ self.m_digest[3] = 0x10325476;
+ self.m_digest[4] = 0xC3D2E1F0;
+ self.m_blockByteIndex = 0;
+ self.m_byteCount = 0;
+ }
+
+ auto process_byte(this sha1 &self, std::uint8_t octet) -> void
+ {
+ self.m_block[self.m_blockByteIndex++] = octet;
+ ++self.m_byteCount;
+
+ if (self.m_blockByteIndex == block_bytes) {
+ self.m_blockByteIndex = 0;
+ self.process_block();
+ }
+ }
+
+ auto process_block(this sha1 &self,
+ void const *const start,
+ void const *const end)
+ -> void
+ {
+ auto *first = static_cast<uint8_t const *>(start);
+ auto *last = static_cast<uint8_t const *>(end);
+
+ while (first != last) {
+ self.process_byte(*first);
+ first++;
+ }
+ }
+
+ auto process_bytes(this sha1 &self,
+ void const *const data,
+ size_t const len)
+ -> void
+ {
+ auto *block = static_cast<uint8_t const *>(data);
+ self.process_block(block, block + len);
+ }
+
+ auto get_digest(this sha1 &self) -> digest32_t
+ {
+ auto const bit_count = self.m_byteCount * 8;
+
+ self.process_byte(0x80);
+ if (self.m_blockByteIndex > 56) {
+ while (self.m_blockByteIndex != 0)
+ self.process_byte(0);
+
+ while (self.m_blockByteIndex < 56)
+ self.process_byte(0);
+ } else {
+ while (self.m_blockByteIndex < 56)
+ self.process_byte(0);
+ }
+
+ self.process_byte(0);
+ self.process_byte(0);
+ self.process_byte(0);
+ self.process_byte(0);
+ self.process_byte(static_cast<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));
+
+ return self.m_digest;
+ }
+
+ 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;
+ }
+
+private:
+ auto process_block(this sha1 &self) -> void
+ {
+ 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]);
+ }
+
+ 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);
+ }
+
+ auto a = self.m_digest[0];
+ auto b = self.m_digest[1];
+ auto c = self.m_digest[2];
+ auto d = self.m_digest[3];
+ auto e = self.m_digest[4];
+
+ for (std::size_t i = 0; i < 80; ++i) {
+ auto f = std::uint32_t{0};
+ auto k = std::uint32_t{0};
+
+ if (i < 20) {
+ f = (b & c) | (~b & d);
+ k = 0x5A827999;
+ } else if (i < 40) {
+ f = b ^ c ^ d;
+ k = 0x6ED9EBA1;
+ } else if (i < 60) {
+ f = (b & c) | (b & d) | (c & d);
+ k = 0x8F1BBCDC;
+ } else {
+ f = b ^ c ^ d;
+ k = 0xCA62C1D6;
+ }
+
+ auto temp = std::uint32_t{left_rotate(a, 5)
+ + f + e + k + w[i]};
+ e = d;
+ d = c;
+ c = left_rotate(b, 30);
+ b = a;
+ a = temp;
+ }
+
+ self.m_digest[0] += a;
+ self.m_digest[1] += b;
+ self.m_digest[2] += c;
+ self.m_digest[3] += d;
+ self.m_digest[4] += e;
+ }
+
+ digest32_t m_digest;
+ std::array<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";
+
+template <>
+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";
+
+template <>
+inline constexpr std::wstring_view guid_encoder<wchar_t> = L"0123456789abcdef";
+
+// ---------------------------------------------------------------------
+// UUID format https://tools.ietf.org/html/rfc4122
+// ---------------------------------------------------------------------
+
+// ---------------------------------------------------------------------
+// Field NDR Data Type Octet # Note
+// Note
+// ---------------------------------------------------------------------
+// time_low unsigned long 0 - 3
+// The low field of the timestamp.
+// time_mid unsigned short 4 - 5
+// The middle field of the timestamp.
+// time_hi_and_version unsigned short 6 - 7
+// The high field of the timestamp multiplexed with the version number.
+// clock_seq_hi_and_reserved unsigned small 8
+// The high field of the clock sequence multiplexed with the variant.
+// clock_seq_low unsigned small 9
+// The low field of the clock sequence.
+// node character 10 - 15
+// The spatially unique node identifier.
+// ---------------------------------------------------------------------
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | time_low |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | time_mid | time_hi_and_version |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |clk_seq_hi_res | clk_seq_low | node (0-1) |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | node (2-5) |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+// ---------------------------------------------------------------------
+// enumerations
+// ---------------------------------------------------------------------
+
+// indicated by a bit pattern in octet 8, marked with N in
+// xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx
+export enum struct uuid_variant {
+ // NCS backward compatibility (with the obsolete Apollo Network
+ // Computing System 1.5 UUID format).
+ // N bit pattern: 0xxx
+ // > the first 6 octets of the UUID are a 48-bit timestamp (the number
+ // of 4 microsecond units of time since 1 Jan 1980 UTC);
+ // > the next 2 octets are reserved;
+ // > the next octet is the "address family";
+ // > the final 7 octets are a 56-bit host ID in the form specified by
+ // the address family
+ ncs,
+
+ // RFC 4122/DCE 1.1
+ // N bit pattern: 10xx
+ // > big-endian byte order
+ rfc,
+
+ // Microsoft Corporation backward compatibility
+ // N bit pattern: 110x
+ // > little endian byte order
+ // > formely used in the Component Object Model (COM) library
+ microsoft,
+
+ // reserved for possible future definition
+ // N bit pattern: 111x
+ reserved
+};
+
+// indicated by a bit pattern in octet 6, marked with M in
+// xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx
+export enum struct uuid_version {
+ // only possible for nil or invalid uuids
+ none = 0,
+ // The time-based version specified in RFC 4122
+ time_based = 1,
+ // DCE Security version, with embedded POSIX UIDs.
+ dce_security = 2,
+ // The name-based version specified in RFS 4122 with MD5 hashing
+ name_based_md5 = 3,
+ // The randomly or pseudo-randomly generated version specified in RFS 4122
+ random_number_based = 4,
+ // The name-based version specified in RFS 4122 with SHA1 hashing
+ name_based_sha1 = 5
+};
+
+// Forward declare uuid and to_string so that we can declare to_string as a
+// friend later.
+export struct uuid;
+export template <typename 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 {
+ using value_type = std::uint8_t;
+
+ constexpr uuid() noexcept = default;
+
+ uuid(value_type(&arr)[16]) noexcept // NOLINT
+ {
+ std::ranges::copy(arr, std::ranges::begin(data));
+ }
+
+ constexpr uuid(std::array<value_type, 16> const &arr) noexcept
+ : data{arr}
+ {
+ }
+
+ explicit uuid(std::span<value_type, 16> bytes)
+ {
+ std::ranges::copy(bytes, std::ranges::begin(data));
+ }
+
+ explicit uuid(std::span<value_type> bytes)
+ {
+ if (bytes.size() != 16)
+ throw std::logic_error("wrong size for uuid");
+ std::ranges::copy(bytes, std::ranges::begin(data));
+ }
+
+ template<typename ForwardIterator>
+ explicit uuid(ForwardIterator first, ForwardIterator last)
+ {
+ if (std::distance(first, last) != 16)
+ throw std::logic_error("wrong size for uuid");
+
+ std::copy(first, last, std::begin(data));
+ }
+
+ [[nodiscard]] constexpr auto variant() const noexcept -> uuid_variant
+ {
+ if ((data[8] & 0x80) == 0x00)
+ return uuid_variant::ncs;
+ else if ((data[8] & 0xC0) == 0x80)
+ return uuid_variant::rfc;
+ else if ((data[8] & 0xE0) == 0xC0)
+ return uuid_variant::microsoft;
+ else
+ return uuid_variant::reserved;
+ }
+
+ [[nodiscard]] constexpr auto version() const noexcept -> uuid_version
+ {
+ if ((data[6] & 0xF0) == 0x10)
+ return uuid_version::time_based;
+ else if ((data[6] & 0xF0) == 0x20)
+ return uuid_version::dce_security;
+ else if ((data[6] & 0xF0) == 0x30)
+ return uuid_version::name_based_md5;
+ else if ((data[6] & 0xF0) == 0x40)
+ return uuid_version::random_number_based;
+ else if ((data[6] & 0xF0) == 0x50)
+ return uuid_version::name_based_sha1;
+ else
+ return uuid_version::none;
+ }
+
+ [[nodiscard]] constexpr auto is_nil() const noexcept -> bool
+ {
+ for (auto i : data)
+ if (i != 0)
+ return false;
+ return true;
+ }
+
+ auto swap(uuid &other) noexcept -> void
+ {
+ data.swap(other.data);
+ }
+
+ [[nodiscard]] inline 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);
+ }
+
+ template <typename StringType>
+ [[nodiscard]] constexpr static auto
+ is_valid_uuid(StringType const &in_str) noexcept
+ -> bool
+ {
+ auto str = to_string_view(in_str);
+ auto firstDigit = true;
+ auto hasBraces = std::size_t{0};
+ auto index = std::size_t{0};
+
+ if (str.empty())
+ return false;
+
+ if (str.front() == '{')
+ hasBraces = 1;
+
+ if (hasBraces && str.back() != '}')
+ return false;
+
+ for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) {
+ if (str[i] == '-')
+ continue;
+
+ if (index >= 16 || !is_hex(str[i]))
+ return false;
+
+ if (firstDigit) {
+ firstDigit = false;
+ } else {
+ index++;
+ firstDigit = true;
+ }
+ }
+
+ if (index < 16)
+ return false;
+
+ return true;
+ }
+
+ template <typename StringType>
+ [[nodiscard]] constexpr static auto
+ from_string(StringType const & in_str) noexcept
+ -> std::optional<uuid>
+ {
+ auto str = to_string_view(in_str);
+ bool firstDigit = true;
+ size_t hasBraces = 0;
+ size_t index = 0;
+
+ std::array<uint8_t, 16> data{ { 0 } };
+
+ if (str.empty())
+ return {};
+
+ if (str.front() == '{')
+ hasBraces = 1;
+ if (hasBraces && str.back() != '}')
+ return {};
+
+ for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) {
+ if (str[i] == '-')
+ continue;
+
+ if (index >= 16 || !is_hex(str[i])) {
+ return {};
+ }
+
+ if (firstDigit) {
+ data[index] = static_cast<uint8_t>(hex2char(str[i]) << 4);
+ firstDigit = false;
+ } else {
+ data[index] = static_cast<uint8_t>(data[index] | hex2char(str[i]));
+ index++;
+ firstDigit = true;
+ }
+ }
+
+ if (index < 16) {
+ return {};
+ }
+
+ return uuid{data};
+ }
+
+private:
+ 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)
+ -> 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>;
+
+ friend std::hash<uuid>;
+};
+
+// --------------------------------------------------------------------------------------------------------------------------
+// operators and non-member functions
+// --------------------------------------------------------------------------------------------------------------------------
+
+export [[nodiscard]]
+auto operator== (uuid const &lhs, uuid const &rhs) noexcept -> bool
+{
+ return lhs.data == rhs.data;
+}
+
+export [[nodiscard]]
+auto operator!= (uuid const &lhs, uuid const &rhs) noexcept -> bool
+{
+ return !(lhs == rhs);
+}
+
+export [[nodiscard]]
+auto operator< (uuid const &lhs, uuid const &rhs) noexcept -> bool
+{
+ return lhs.data < rhs.data;
+}
+
+export template <typename CharT, typename Traits, typename 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>);
+
+ 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];
+ index++;
+ }
+
+ return uustr;
+}
+
+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;
+}
+
+export auto swap(uuid &lhs, uuid &rhs) noexcept -> void
+{
+ lhs.swap(rhs);
+}
+
+/***********************************************************************
+ * namespace IDs that could be used for generating name-based uuids
+ */
+
+// Name string is a fully-qualified domain name
+export uuid uuid_namespace_dns{{
+ 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
+}};
+
+// Name string is a URL
+export uuid uuid_namespace_url{{
+ 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
+}};
+
+// Name string is an ISO OID (See https://oidref.com/,
+// https://en.wikipedia.org/wiki/Object_identifier)
+export uuid uuid_namespace_oid{{
+ 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
+}};
+
+// Name string is an X.500 DN, in DER or a text output format (See
+// https://en.wikipedia.org/wiki/X.500,
+// https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One)
+export uuid uuid_namespace_x500{{
+ 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
+}};
+
+/***********************************************************************
+ * uuid generators
+ */
+
+export template <typename UniformRandomNumberGenerator>
+struct basic_uuid_random_generator
+{
+ using engine_type = UniformRandomNumberGenerator;
+
+ explicit basic_uuid_random_generator(engine_type &gen)
+ : generator(&gen, [](auto) {})
+ {
+ }
+
+ explicit basic_uuid_random_generator(engine_type *gen)
+ : generator(gen, [](auto) {})
+ {
+ }
+
+ [[nodiscard]] auto operator()() -> uuid
+ {
+ 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);
+
+ // variant must be 10xxxxxx
+ bytes[8] &= 0xBF;
+ bytes[8] |= 0x80;
+
+ // version must be 0100xxxx
+ bytes[6] &= 0x4F;
+ bytes[6] |= 0x40;
+
+ return uuid{std::begin(bytes), std::end(bytes)};
+ }
+
+private:
+ std::uniform_int_distribution<std::uint32_t> distribution;
+ std::shared_ptr<UniformRandomNumberGenerator> generator;
+};
+
+export using uuid_random_generator = basic_uuid_random_generator<std::mt19937>;
+
+export struct uuid_name_generator
+{
+ explicit uuid_name_generator(uuid const &namespace_uuid) noexcept
+ : nsuuid(namespace_uuid)
+ {
+ }
+
+ template <typename StringType>
+ [[nodiscard]] auto operator()(StringType const &name) -> uuid
+ {
+ reset();
+ process_characters(to_string_view(name));
+ return make_uuid();
+ }
+
+private:
+ auto reset() -> void
+ {
+ hasher.reset();
+
+ auto nsbytes = nsuuid.as_bytes();
+
+ auto bytes = std::array<std::byte, 16>();
+ std::ranges::copy(nsbytes, std::ranges::begin(bytes));
+
+ hasher.process_bytes(bytes.data(), bytes.size());
+ }
+
+ template <typename CharT, typename Traits>
+ 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));
+ 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));
+ }
+ }
+ }
+
+ [[nodiscard]] auto make_uuid() -> uuid
+ {
+ auto digest = hasher.get_digest_bytes();
+
+ // variant must be 0b10xxxxxx
+ digest[8] &= 0xBF;
+ digest[8] |= 0x80;
+
+ // version must be 0b0101xxxx
+ digest[6] &= 0x5F;
+ digest[6] |= 0x50;
+
+ return uuid(std::span(digest).subspan(0, 16));
+ }
+
+private:
+ uuid nsuuid;
+ sha1 hasher;
+};
+
+/*
+ * Create a random UUID.
+ */
+export auto random_uuid() -> uuid
+{
+ auto rd = std::random_device();
+ 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 generator = std::mt19937(seq);
+ auto gen = uuid_random_generator{generator};
+
+ return gen();
+}
+
+} // namespace nihil
+
+namespace std {
+
+export template <>
+struct hash<nihil::uuid>
+{
+ using argument_type = nihil::uuid;
+ using result_type = std::size_t;
+
+ [[nodiscard]] auto operator()(argument_type const &uuid) const
+ -> 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]);
+
+ return std::hash<std::uint64_t>{}(l ^ h);
+ }
+};
+
+} // namespace std