From 47b935e2c85a794d60c22e8d96f53c0a2d364977 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 13:55:03 +0200 Subject: kstd: rename kstd header to include hpp --- libs/acpi/acpi/common/table_header.cpp | 4 +- libs/acpi/acpi/common/table_header.hpp | 2 +- libs/acpi/acpi/common/table_header.test.cpp | 2 +- libs/acpi/acpi/data/madt.cpp | 2 +- libs/acpi/acpi/data/madt.hpp | 4 +- libs/acpi/acpi/data/madt.test.cpp | 2 +- libs/acpi/acpi/data/rsdt.cpp | 2 +- libs/acpi/acpi/data/rsdt.test.cpp | 2 +- libs/acpi/acpi/data/xsdt.cpp | 2 +- libs/acpi/acpi/data/xsdt.test.cpp | 2 +- libs/acpi/acpi/pointers.cpp | 2 +- libs/acpi/acpi/pointers.hpp | 2 +- libs/kstd/kstd/allocator | 64 -- libs/kstd/kstd/allocator.hpp | 64 ++ libs/kstd/kstd/asm_ptr | 78 -- libs/kstd/kstd/asm_ptr.hpp | 78 ++ libs/kstd/kstd/bits/basic_string.hpp | 4 +- libs/kstd/kstd/bits/format/arg.hpp | 2 +- libs/kstd/kstd/bits/format/args.hpp | 2 +- libs/kstd/kstd/bits/format/context.hpp | 2 +- libs/kstd/kstd/bits/format/formatter.hpp | 2 +- libs/kstd/kstd/bits/format/output_buffer.hpp | 2 +- libs/kstd/kstd/bits/format/parse_context.hpp | 2 +- libs/kstd/kstd/bits/format/string.hpp | 2 +- libs/kstd/kstd/bits/format/vformat.cpp | 4 +- libs/kstd/kstd/bits/format/vformat.hpp | 2 +- libs/kstd/kstd/bits/observer_ptr.hpp | 2 +- libs/kstd/kstd/bits/observer_ptr.test.cpp | 2 +- libs/kstd/kstd/bits/print_sink.hpp | 2 +- libs/kstd/kstd/bits/shared_ptr.hpp | 2 +- libs/kstd/kstd/bits/unique_ptr.hpp | 2 +- libs/kstd/kstd/cstring | 21 - libs/kstd/kstd/cstring.hpp | 21 + libs/kstd/kstd/ext/bitfield_enum | 65 -- libs/kstd/kstd/ext/bitfield_enum.hpp | 65 ++ libs/kstd/kstd/flat_map | 406 --------- libs/kstd/kstd/flat_map.hpp | 406 +++++++++ libs/kstd/kstd/flat_map.test.cpp | 2 +- libs/kstd/kstd/format | 24 - libs/kstd/kstd/format.hpp | 24 + libs/kstd/kstd/format.test.cpp | 2 +- libs/kstd/kstd/libc/string.cpp | 2 +- libs/kstd/kstd/memory | 8 - libs/kstd/kstd/memory.hpp | 8 + libs/kstd/kstd/mutex | 101 --- libs/kstd/kstd/mutex.cpp | 2 +- libs/kstd/kstd/mutex.hpp | 101 +++ libs/kstd/kstd/print | 69 -- libs/kstd/kstd/print.hpp | 69 ++ libs/kstd/kstd/ranges | 21 - libs/kstd/kstd/ranges.hpp | 21 + libs/kstd/kstd/stack | 192 ---- libs/kstd/kstd/stack.hpp | 192 ++++ libs/kstd/kstd/string | 60 -- libs/kstd/kstd/string.hpp | 60 ++ libs/kstd/kstd/string.test.cpp | 4 +- libs/kstd/kstd/units | 149 ---- libs/kstd/kstd/units.hpp | 149 ++++ libs/kstd/kstd/vector | 1079 ----------------------- libs/kstd/kstd/vector.hpp | 1079 +++++++++++++++++++++++ libs/kstd/kstd/vector.test.cpp | 4 +- libs/multiboot2/multiboot2/information.hpp | 2 +- libs/multiboot2/multiboot2/information/data.hpp | 2 +- 63 files changed, 2378 insertions(+), 2378 deletions(-) delete mode 100644 libs/kstd/kstd/allocator create mode 100644 libs/kstd/kstd/allocator.hpp delete mode 100644 libs/kstd/kstd/asm_ptr create mode 100644 libs/kstd/kstd/asm_ptr.hpp delete mode 100644 libs/kstd/kstd/cstring create mode 100644 libs/kstd/kstd/cstring.hpp delete mode 100644 libs/kstd/kstd/ext/bitfield_enum create mode 100644 libs/kstd/kstd/ext/bitfield_enum.hpp delete mode 100644 libs/kstd/kstd/flat_map create mode 100644 libs/kstd/kstd/flat_map.hpp delete mode 100644 libs/kstd/kstd/format create mode 100644 libs/kstd/kstd/format.hpp delete mode 100644 libs/kstd/kstd/memory create mode 100644 libs/kstd/kstd/memory.hpp delete mode 100644 libs/kstd/kstd/mutex create mode 100644 libs/kstd/kstd/mutex.hpp delete mode 100644 libs/kstd/kstd/print create mode 100644 libs/kstd/kstd/print.hpp delete mode 100644 libs/kstd/kstd/ranges create mode 100644 libs/kstd/kstd/ranges.hpp delete mode 100644 libs/kstd/kstd/stack create mode 100644 libs/kstd/kstd/stack.hpp delete mode 100644 libs/kstd/kstd/string create mode 100644 libs/kstd/kstd/string.hpp delete mode 100644 libs/kstd/kstd/units create mode 100644 libs/kstd/kstd/units.hpp delete mode 100644 libs/kstd/kstd/vector create mode 100644 libs/kstd/kstd/vector.hpp (limited to 'libs') diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp index 6a7a4dc3..ebceca1d 100644 --- a/libs/acpi/acpi/common/table_header.cpp +++ b/libs/acpi/acpi/common/table_header.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp index 471fed87..8e1d7c59 100644 --- a/libs/acpi/acpi/common/table_header.hpp +++ b/libs/acpi/acpi/common/table_header.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include +#include #include #include diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp index ddc879ed..bbd42bd6 100644 --- a/libs/acpi/acpi/common/table_header.test.cpp +++ b/libs/acpi/acpi/common/table_header.test.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp index 1a8b6d3a..fe6bae16 100644 --- a/libs/acpi/acpi/data/madt.cpp +++ b/libs/acpi/acpi/data/madt.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp index b76daa4f..4d992613 100644 --- a/libs/acpi/acpi/data/madt.hpp +++ b/libs/acpi/acpi/data/madt.hpp @@ -7,9 +7,9 @@ #include #include -#include +#include #include -#include +#include #include #include diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp index 1b95a74b..9ec0180a 100644 --- a/libs/acpi/acpi/data/madt.test.cpp +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp index 80e209db..a8626742 100644 --- a/libs/acpi/acpi/data/rsdt.cpp +++ b/libs/acpi/acpi/data/rsdt.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp index 47992ce6..826d0b4f 100644 --- a/libs/acpi/acpi/data/rsdt.test.cpp +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp index b77aeab7..c4b109b8 100644 --- a/libs/acpi/acpi/data/xsdt.cpp +++ b/libs/acpi/acpi/data/xsdt.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp index 77a53408..19d9bcdb 100644 --- a/libs/acpi/acpi/data/xsdt.test.cpp +++ b/libs/acpi/acpi/data/xsdt.test.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp index 2ac8d319..0393292c 100644 --- a/libs/acpi/acpi/pointers.cpp +++ b/libs/acpi/acpi/pointers.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/libs/acpi/acpi/pointers.hpp b/libs/acpi/acpi/pointers.hpp index 5771e7df..310447f5 100644 --- a/libs/acpi/acpi/pointers.hpp +++ b/libs/acpi/acpi/pointers.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include +#include #include #include diff --git a/libs/kstd/kstd/allocator b/libs/kstd/kstd/allocator deleted file mode 100644 index 0de0e10d..00000000 --- a/libs/kstd/kstd/allocator +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef KSTD_ALLOCATOR_HPP -#define KSTD_ALLOCATOR_HPP - -#include -#include -#include - -#if __has_builtin(__builtin_operator_new) >= 201'802L -#define KSTD_OPERATOR_NEW __builtin_operator_new -#define KSTD_OPERATOR_DELETE __builtin_operator_delete -#else -#define KSTD_OPERATOR_NEW ::operator new -#define KSTD_OPERATOR_DELETE ::operator delete -#endif - -namespace kstd -{ - - template - struct allocator - { - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; - using is_always_equal = std::true_type; - - constexpr allocator() noexcept = default; - - template - constexpr allocator(allocator const &) noexcept - {} - - constexpr auto allocate(std::size_t n) -> T * - { - if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) - { - return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)})); - } - return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T))); - } - - constexpr void deallocate(T * p, std::size_t n) noexcept - { - if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) - { - KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)}); - } - KSTD_OPERATOR_DELETE(p, n * sizeof(T)); - } - }; - - template - constexpr auto operator==(allocator const &, allocator const &) noexcept -> bool - { - return true; - } - -} // namespace kstd - -#undef KSTD_OPERATOR_NEW -#undef KSTD_OPERATOR_DELETE - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/allocator.hpp b/libs/kstd/kstd/allocator.hpp new file mode 100644 index 00000000..0de0e10d --- /dev/null +++ b/libs/kstd/kstd/allocator.hpp @@ -0,0 +1,64 @@ +#ifndef KSTD_ALLOCATOR_HPP +#define KSTD_ALLOCATOR_HPP + +#include +#include +#include + +#if __has_builtin(__builtin_operator_new) >= 201'802L +#define KSTD_OPERATOR_NEW __builtin_operator_new +#define KSTD_OPERATOR_DELETE __builtin_operator_delete +#else +#define KSTD_OPERATOR_NEW ::operator new +#define KSTD_OPERATOR_DELETE ::operator delete +#endif + +namespace kstd +{ + + template + struct allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::true_type; + + constexpr allocator() noexcept = default; + + template + constexpr allocator(allocator const &) noexcept + {} + + constexpr auto allocate(std::size_t n) -> T * + { + if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)})); + } + return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T))); + } + + constexpr void deallocate(T * p, std::size_t n) noexcept + { + if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)}); + } + KSTD_OPERATOR_DELETE(p, n * sizeof(T)); + } + }; + + template + constexpr auto operator==(allocator const &, allocator const &) noexcept -> bool + { + return true; + } + +} // namespace kstd + +#undef KSTD_OPERATOR_NEW +#undef KSTD_OPERATOR_DELETE + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/asm_ptr b/libs/kstd/kstd/asm_ptr deleted file mode 100644 index c06a8b52..00000000 --- a/libs/kstd/kstd/asm_ptr +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef KSTD_ASM_POINTER_HPP -#define KSTD_ASM_POINTER_HPP - -#include -#include - -namespace kstd -{ - - /** - * @brief A pointer that is defined in some assembly source file. - * - * @tparam Type The type of the pointer - */ - template - struct asm_ptr - { - using value_type = Type; - using pointer = value_type *; - using const_pointer = value_type const *; - using reference = value_type &; - using const_reference = value_type const &; - - asm_ptr() = delete; - asm_ptr(asm_ptr const &) = delete; - asm_ptr(asm_ptr &&) = delete; - ~asm_ptr() = delete; - - constexpr auto operator=(asm_ptr const &) = delete; - constexpr auto operator=(asm_ptr &&) = delete; - - auto get() const noexcept -> pointer - { - return m_ptr; - } - - constexpr auto operator+(std::ptrdiff_t offset) const noexcept -> pointer - { - return std::bit_cast(m_ptr) + offset; - } - - constexpr auto operator*() noexcept -> reference - { - return *(std::bit_cast(m_ptr)); - } - - constexpr auto operator*() const noexcept -> const_reference - { - return *(std::bit_cast(m_ptr)); - } - - constexpr auto operator[](std::ptrdiff_t offset) noexcept -> reference - { - return *(*this + offset); - } - - constexpr auto operator[](std::ptrdiff_t offset) const noexcept -> const_reference - { - return *(*this + offset); - } - - constexpr auto operator->() noexcept -> pointer - { - return m_ptr; - } - - constexpr auto operator->() const noexcept -> const_pointer - { - return m_ptr; - } - - private: - pointer m_ptr; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/asm_ptr.hpp b/libs/kstd/kstd/asm_ptr.hpp new file mode 100644 index 00000000..c06a8b52 --- /dev/null +++ b/libs/kstd/kstd/asm_ptr.hpp @@ -0,0 +1,78 @@ +#ifndef KSTD_ASM_POINTER_HPP +#define KSTD_ASM_POINTER_HPP + +#include +#include + +namespace kstd +{ + + /** + * @brief A pointer that is defined in some assembly source file. + * + * @tparam Type The type of the pointer + */ + template + struct asm_ptr + { + using value_type = Type; + using pointer = value_type *; + using const_pointer = value_type const *; + using reference = value_type &; + using const_reference = value_type const &; + + asm_ptr() = delete; + asm_ptr(asm_ptr const &) = delete; + asm_ptr(asm_ptr &&) = delete; + ~asm_ptr() = delete; + + constexpr auto operator=(asm_ptr const &) = delete; + constexpr auto operator=(asm_ptr &&) = delete; + + auto get() const noexcept -> pointer + { + return m_ptr; + } + + constexpr auto operator+(std::ptrdiff_t offset) const noexcept -> pointer + { + return std::bit_cast(m_ptr) + offset; + } + + constexpr auto operator*() noexcept -> reference + { + return *(std::bit_cast(m_ptr)); + } + + constexpr auto operator*() const noexcept -> const_reference + { + return *(std::bit_cast(m_ptr)); + } + + constexpr auto operator[](std::ptrdiff_t offset) noexcept -> reference + { + return *(*this + offset); + } + + constexpr auto operator[](std::ptrdiff_t offset) const noexcept -> const_reference + { + return *(*this + offset); + } + + constexpr auto operator->() noexcept -> pointer + { + return m_ptr; + } + + constexpr auto operator->() const noexcept -> const_pointer + { + return m_ptr; + } + + private: + pointer m_ptr; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp index 1675434c..a718bccc 100644 --- a/libs/kstd/kstd/bits/basic_string.hpp +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -1,10 +1,10 @@ #ifndef KSTD_BITS_BASIC_STRING_HPP #define KSTD_BITS_BASIC_STRING_HPP -#include +#include #include #include -#include +#include #include #include diff --git a/libs/kstd/kstd/bits/format/arg.hpp b/libs/kstd/kstd/bits/format/arg.hpp index e65b26fa..8271ca3d 100644 --- a/libs/kstd/kstd/bits/format/arg.hpp +++ b/libs/kstd/kstd/bits/format/arg.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_ARG_HPP #define KSTD_BITS_FORMAT_ARG_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/format/args.hpp b/libs/kstd/kstd/bits/format/args.hpp index e8e31145..01735011 100644 --- a/libs/kstd/kstd/bits/format/args.hpp +++ b/libs/kstd/kstd/bits/format/args.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_ARGS_HPP #define KSTD_BITS_FORMAT_ARGS_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/format/context.hpp b/libs/kstd/kstd/bits/format/context.hpp index c166ba9c..d5f2f4d9 100644 --- a/libs/kstd/kstd/bits/format/context.hpp +++ b/libs/kstd/kstd/bits/format/context.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_CONTEXT_HPP #define KSTD_BITS_FORMAT_CONTEXT_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/format/formatter.hpp b/libs/kstd/kstd/bits/format/formatter.hpp index eb288294..c26907b2 100644 --- a/libs/kstd/kstd/bits/format/formatter.hpp +++ b/libs/kstd/kstd/bits/format/formatter.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMATTER_HPP #define KSTD_BITS_FORMATTER_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/format/output_buffer.hpp b/libs/kstd/kstd/bits/format/output_buffer.hpp index fd7a2b4c..7e6f0575 100644 --- a/libs/kstd/kstd/bits/format/output_buffer.hpp +++ b/libs/kstd/kstd/bits/format/output_buffer.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP #define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include diff --git a/libs/kstd/kstd/bits/format/parse_context.hpp b/libs/kstd/kstd/bits/format/parse_context.hpp index cab8d726..81ed52cf 100644 --- a/libs/kstd/kstd/bits/format/parse_context.hpp +++ b/libs/kstd/kstd/bits/format/parse_context.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP #define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp index a2b298d4..ab034e7c 100644 --- a/libs/kstd/kstd/bits/format/string.hpp +++ b/libs/kstd/kstd/bits/format/string.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_STRING_HPP #define KSTD_BITS_FORMAT_STRING_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/format/vformat.cpp b/libs/kstd/kstd/bits/format/vformat.cpp index 1bf293a4..495f0a10 100644 --- a/libs/kstd/kstd/bits/format/vformat.cpp +++ b/libs/kstd/kstd/bits/format/vformat.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp index 2db56d17..829e0ea4 100644 --- a/libs/kstd/kstd/bits/format/vformat.hpp +++ b/libs/kstd/kstd/bits/format/vformat.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_VFORMAT_HPP #define KSTD_BITS_FORMAT_VFORMAT_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include #include diff --git a/libs/kstd/kstd/bits/observer_ptr.hpp b/libs/kstd/kstd/bits/observer_ptr.hpp index 2593d7a4..e63e5d77 100644 --- a/libs/kstd/kstd/bits/observer_ptr.hpp +++ b/libs/kstd/kstd/bits/observer_ptr.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_OBSERVER_PTR_HPP #define KSTD_OBSERVER_PTR_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include #include diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp index f26fc09a..eca14ccd 100644 --- a/libs/kstd/kstd/bits/observer_ptr.test.cpp +++ b/libs/kstd/kstd/bits/observer_ptr.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/libs/kstd/kstd/bits/print_sink.hpp b/libs/kstd/kstd/bits/print_sink.hpp index af765e06..a1e9b0d4 100644 --- a/libs/kstd/kstd/bits/print_sink.hpp +++ b/libs/kstd/kstd/bits/print_sink.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_PRINT_SINK_HPP #define KSTD_BITS_PRINT_SINK_HPP -// IWYU pragma: private, include +// IWYU pragma: private, include namespace kstd { diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp index 57a89c0d..ce4bfb7f 100644 --- a/libs/kstd/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/kstd/bits/shared_ptr.hpp @@ -6,7 +6,7 @@ #include #include -// IWYU pragma: private, include +// IWYU pragma: private, include namespace kstd { diff --git a/libs/kstd/kstd/bits/unique_ptr.hpp b/libs/kstd/kstd/bits/unique_ptr.hpp index 3d803b4a..1df18cf2 100644 --- a/libs/kstd/kstd/bits/unique_ptr.hpp +++ b/libs/kstd/kstd/bits/unique_ptr.hpp @@ -3,7 +3,7 @@ #include -// IWYU pragma: private, include +// IWYU pragma: private, include namespace kstd { diff --git a/libs/kstd/kstd/cstring b/libs/kstd/kstd/cstring deleted file mode 100644 index bd8b28d8..00000000 --- a/libs/kstd/kstd/cstring +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef KSTD_CSTRING -#define KSTD_CSTRING - -#include - -namespace kstd::libc -{ - - extern "C" - { - auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *; - auto memset(void * dest, int value, std::size_t size) noexcept -> void *; - auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *; - auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int; - - auto strlen(char const * string) noexcept -> std::size_t; - } - -} // namespace kstd::libc - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/cstring.hpp b/libs/kstd/kstd/cstring.hpp new file mode 100644 index 00000000..bd8b28d8 --- /dev/null +++ b/libs/kstd/kstd/cstring.hpp @@ -0,0 +1,21 @@ +#ifndef KSTD_CSTRING +#define KSTD_CSTRING + +#include + +namespace kstd::libc +{ + + extern "C" + { + auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *; + auto memset(void * dest, int value, std::size_t size) noexcept -> void *; + auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *; + auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int; + + auto strlen(char const * string) noexcept -> std::size_t; + } + +} // namespace kstd::libc + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ext/bitfield_enum b/libs/kstd/kstd/ext/bitfield_enum deleted file mode 100644 index 80fe9d29..00000000 --- a/libs/kstd/kstd/ext/bitfield_enum +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef KSTD_EXT_BITFIELD_ENUM_HPP -#define KSTD_EXT_BITFIELD_ENUM_HPP - -#include -#include -#include - -namespace kstd::ext -{ - - template - requires std::is_enum_v - struct is_bitfield_enum : std::false_type - { - }; - - //! @concept Specifies that an enum is to be used to define bits in a bitfield. - template - concept bitfield_enum = is_bitfield_enum::value; - -}; // namespace kstd::ext - -template -constexpr auto operator|(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); -} - -template -constexpr auto operator|=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs | rhs; -} - -template -constexpr auto operator&(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); -} - -template -constexpr auto operator&=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs & rhs; -} - -template -constexpr auto operator^(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); -} - -template -constexpr auto operator^=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs ^ rhs; -} - -template -constexpr auto operator~(EnumType lhs) -> EnumType -{ - return std::bit_cast(~std::to_underlying(lhs)); -} - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ext/bitfield_enum.hpp b/libs/kstd/kstd/ext/bitfield_enum.hpp new file mode 100644 index 00000000..80fe9d29 --- /dev/null +++ b/libs/kstd/kstd/ext/bitfield_enum.hpp @@ -0,0 +1,65 @@ +#ifndef KSTD_EXT_BITFIELD_ENUM_HPP +#define KSTD_EXT_BITFIELD_ENUM_HPP + +#include +#include +#include + +namespace kstd::ext +{ + + template + requires std::is_enum_v + struct is_bitfield_enum : std::false_type + { + }; + + //! @concept Specifies that an enum is to be used to define bits in a bitfield. + template + concept bitfield_enum = is_bitfield_enum::value; + +}; // namespace kstd::ext + +template +constexpr auto operator|(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); +} + +template +constexpr auto operator|=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs | rhs; +} + +template +constexpr auto operator&(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); +} + +template +constexpr auto operator&=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs & rhs; +} + +template +constexpr auto operator^(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); +} + +template +constexpr auto operator^=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs ^ rhs; +} + +template +constexpr auto operator~(EnumType lhs) -> EnumType +{ + return std::bit_cast(~std::to_underlying(lhs)); +} + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map deleted file mode 100644 index e51eb918..00000000 --- a/libs/kstd/kstd/flat_map +++ /dev/null @@ -1,406 +0,0 @@ -#ifndef KSTD_FLAT_MAP_HPP -#define KSTD_FLAT_MAP_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - template, - typename KeyContainerType = kstd::vector, typename MappedContainerType = kstd::vector> - struct flat_map - { - //! The type of container used to store the map keys. - using key_container_type = KeyContainerType; - - //! The type of container used to store the map values. - using mapped_container_type = MappedContainerType; - - //! The type of the map keys. - using key_type = KeyType; - - //! The type of the mappe values. - using mapped_type = MappedType; - - //! The type of a single key-value value in the map. - using value_type = std::pair; - - //! The comparator used to sort the keys. - using key_compare = KeyCompare; - - using reference = std::pair; - using const_reference = std::pair; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using iterator = bits::flat_map_iterator; - using const_iterator = - bits::flat_map_iterator; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - using containers = struct - { - key_container_type keys; - mapped_container_type values; - }; - - //! Compare two object of type value_type. - struct value_compare - { - constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool - { - return lhs.first < rhs.first; - } - }; - - //! Construct an empty flat map. - constexpr flat_map() - : flat_map{key_compare{}} - {} - - //! Construct an empty flat map using the given custom comparator. - //! - //! @param comparator The comparator to use for comparing keys. - constexpr explicit flat_map(key_compare const & comparator) - : m_containers{} - , m_comparator{comparator} - {} - - //! Replace the contents of this flat map with the one of a different one. - //! - //! @param other the flat map to copy from. - //! @return A reference to this flat map. - auto operator=(flat_map const & other) -> flat_map & - { - if (this != &other) - { - std::tie(m_containers, m_comparator) = std::tie(other.m_containers, other.m_comparator); - } - return *this; - } - - //! Get a reference to the mapped value associated with the given key. - //! - //! @warning This function will panic if the key is not found. - //! @param key The key to look up. - //! @return A reference to the mapped value. - [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type & - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return *(m_containers.values.begin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! - //! @warning This function will panic if the key is not found. - //! @param key The key to look up. - //! @return A const reference to the mapped value. - [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const & - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return *(m_containers.values.cbegin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @warning This function will panic if the key is not found. - //! @param x The key to look up. - //! @return A reference to the mapped value. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] constexpr auto at(K const & x) -> mapped_type & - { - auto found = find(x); - if (found != end()) - { - auto offset = std::distance(m_containers.keys.begin(), found.key_iterator()); - return *(m_containers.values.begin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @warning This function will panic if the key is not found. - //! @param x The key to look up. - //! @return A const reference to the mapped value. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto at(K const & x) const -> mapped_type const & - { - auto found = find(x); - if (found != end()) - { - auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator()); - return *(m_containers.values.cbegin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get an iterator to the first element. - [[nodiscard]] auto begin() noexcept -> iterator - { - return iterator{m_containers.keys.begin(), m_containers.values.begin()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto begin() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto cbegin() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto end() noexcept -> iterator - { - return iterator{m_containers.keys.end(), m_containers.values.end()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto end() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto cend() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto rbegin() noexcept -> reverse_iterator - { - return reverse_iterator{end()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cend()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cend()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto rend() noexcept -> reverse_iterator - { - return reverse_iterator{begin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cbegin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cbegin()}; - } - - //! Check whether this flat map is empty or not - [[nodiscard]] auto empty() const noexcept -> bool - { - return m_containers.keys.empty(); - } - - //! Get the number of elements in this flat map. - [[nodiscard]] auto size() const noexcept -> size_type - { - return m_containers.keys.size(); - } - - //! Get the maximum number of elements possible in this flat map - [[nodiscard]] auto max_size() const noexcept -> size_type - { - return std::min(m_containers.keys.max_size(), m_containers.values.max_size()); - } - - //! Try to insert a new key-value pair into the map. - //! - //! @param args Arguments to use for constructing the key-value pair. - //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place. - template - auto emplace(Args &&... args) -> std::pair - requires std::constructible_from - { - auto value = value_type{std::forward(args)...}; - auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator); - - if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return { - iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}, - false - }; - } - - auto offset = std::distance(m_containers.keys.begin(), found); - auto key_iterator = m_containers.keys.begin() + offset; - auto mapped_iterator = m_containers.values.begin() + offset; - - auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first)); - auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second)); - - return { - iterator{inserted_key, inserted_mapped}, - true - }; - } - - //! Try to insert a element for the given key into this map. - //! - //! This function does nothing if the key is already present. - //! - //! @param key The key to insert a value for. - //! @param args The arguments to use to construct the mapped value. - template - auto try_emplace(key_type const & key, Args &&... args) -> std::pair - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found)) - { - return {found, false}; - } - - auto offset = std::distance(m_containers.keys.cbegin(), found); - auto intersertion_point = m_containers.value.begin() + offset; - - auto inserted_key = m_containers.keys.emplace(key); - auto inserted_mapped = m_containers.values.emplace(std::forward(args)...); - - return { - iterator{inserted_key, inserted_mapped}, - true - }; - } - - //! Find an element with an equivalent key. - //! - //! @param key The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - [[nodiscard]] auto find(key_type const & key) noexcept -> iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; - } - return end(); - } - - //! Find an element with an equivalent key. - //! - //! @param key The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; - } - return cend(); - } - - //! Find an element with an equivalent key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @param x The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto find(K const & x) noexcept -> iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; - } - return end(); - } - - //! Find an element with an equivalent key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @param x The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; - } - return cend(); - } - - //! Check if the map contains the given key. - //! - //! @param key The key to check. - //! @return true iff. the key is found, false otherwise. - [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool - { - return find(key) != cend(); - } - - //! Get a reference to the keys container. - //! - //! @return a reference to the keys container. - [[nodiscard]] constexpr auto keys() const noexcept -> key_container_type const & - { - return m_containers.keys; - } - - //! Get a reference to the values container. - //! - //! @return a reference to the values container. - [[nodiscard]] constexpr auto values() const noexcept -> mapped_container_type const & - { - return m_containers.values; - } - - private: - containers m_containers; - key_compare m_comparator; - }; -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map.hpp b/libs/kstd/kstd/flat_map.hpp new file mode 100644 index 00000000..31557760 --- /dev/null +++ b/libs/kstd/kstd/flat_map.hpp @@ -0,0 +1,406 @@ +#ifndef KSTD_FLAT_MAP_HPP +#define KSTD_FLAT_MAP_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template, + typename KeyContainerType = kstd::vector, typename MappedContainerType = kstd::vector> + struct flat_map + { + //! The type of container used to store the map keys. + using key_container_type = KeyContainerType; + + //! The type of container used to store the map values. + using mapped_container_type = MappedContainerType; + + //! The type of the map keys. + using key_type = KeyType; + + //! The type of the mappe values. + using mapped_type = MappedType; + + //! The type of a single key-value value in the map. + using value_type = std::pair; + + //! The comparator used to sort the keys. + using key_compare = KeyCompare; + + using reference = std::pair; + using const_reference = std::pair; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using iterator = bits::flat_map_iterator; + using const_iterator = + bits::flat_map_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using containers = struct + { + key_container_type keys; + mapped_container_type values; + }; + + //! Compare two object of type value_type. + struct value_compare + { + constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool + { + return lhs.first < rhs.first; + } + }; + + //! Construct an empty flat map. + constexpr flat_map() + : flat_map{key_compare{}} + {} + + //! Construct an empty flat map using the given custom comparator. + //! + //! @param comparator The comparator to use for comparing keys. + constexpr explicit flat_map(key_compare const & comparator) + : m_containers{} + , m_comparator{comparator} + {} + + //! Replace the contents of this flat map with the one of a different one. + //! + //! @param other the flat map to copy from. + //! @return A reference to this flat map. + auto operator=(flat_map const & other) -> flat_map & + { + if (this != &other) + { + std::tie(m_containers, m_comparator) = std::tie(other.m_containers, other.m_comparator); + } + return *this; + } + + //! Get a reference to the mapped value associated with the given key. + //! + //! @warning This function will panic if the key is not found. + //! @param key The key to look up. + //! @return A reference to the mapped value. + [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type & + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return *(m_containers.values.begin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! + //! @warning This function will panic if the key is not found. + //! @param key The key to look up. + //! @return A const reference to the mapped value. + [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const & + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return *(m_containers.values.cbegin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @warning This function will panic if the key is not found. + //! @param x The key to look up. + //! @return A reference to the mapped value. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] constexpr auto at(K const & x) -> mapped_type & + { + auto found = find(x); + if (found != end()) + { + auto offset = std::distance(m_containers.keys.begin(), found.key_iterator()); + return *(m_containers.values.begin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @warning This function will panic if the key is not found. + //! @param x The key to look up. + //! @return A const reference to the mapped value. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto at(K const & x) const -> mapped_type const & + { + auto found = find(x); + if (found != end()) + { + auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator()); + return *(m_containers.values.cbegin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get an iterator to the first element. + [[nodiscard]] auto begin() noexcept -> iterator + { + return iterator{m_containers.keys.begin(), m_containers.values.begin()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto begin() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto cbegin() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto end() noexcept -> iterator + { + return iterator{m_containers.keys.end(), m_containers.values.end()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto end() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto cend() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto rbegin() noexcept -> reverse_iterator + { + return reverse_iterator{end()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cend()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cend()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto rend() noexcept -> reverse_iterator + { + return reverse_iterator{begin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cbegin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cbegin()}; + } + + //! Check whether this flat map is empty or not + [[nodiscard]] auto empty() const noexcept -> bool + { + return m_containers.keys.empty(); + } + + //! Get the number of elements in this flat map. + [[nodiscard]] auto size() const noexcept -> size_type + { + return m_containers.keys.size(); + } + + //! Get the maximum number of elements possible in this flat map + [[nodiscard]] auto max_size() const noexcept -> size_type + { + return std::min(m_containers.keys.max_size(), m_containers.values.max_size()); + } + + //! Try to insert a new key-value pair into the map. + //! + //! @param args Arguments to use for constructing the key-value pair. + //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place. + template + auto emplace(Args &&... args) -> std::pair + requires std::constructible_from + { + auto value = value_type{std::forward(args)...}; + auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator); + + if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return { + iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}, + false + }; + } + + auto offset = std::distance(m_containers.keys.begin(), found); + auto key_iterator = m_containers.keys.begin() + offset; + auto mapped_iterator = m_containers.values.begin() + offset; + + auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first)); + auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second)); + + return { + iterator{inserted_key, inserted_mapped}, + true + }; + } + + //! Try to insert a element for the given key into this map. + //! + //! This function does nothing if the key is already present. + //! + //! @param key The key to insert a value for. + //! @param args The arguments to use to construct the mapped value. + template + auto try_emplace(key_type const & key, Args &&... args) -> std::pair + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found)) + { + return {found, false}; + } + + auto offset = std::distance(m_containers.keys.cbegin(), found); + auto intersertion_point = m_containers.value.begin() + offset; + + auto inserted_key = m_containers.keys.emplace(key); + auto inserted_mapped = m_containers.values.emplace(std::forward(args)...); + + return { + iterator{inserted_key, inserted_mapped}, + true + }; + } + + //! Find an element with an equivalent key. + //! + //! @param key The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + [[nodiscard]] auto find(key_type const & key) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; + } + return end(); + } + + //! Find an element with an equivalent key. + //! + //! @param key The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; + } + return cend(); + } + + //! Find an element with an equivalent key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @param x The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto find(K const & x) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; + } + return end(); + } + + //! Find an element with an equivalent key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @param x The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; + } + return cend(); + } + + //! Check if the map contains the given key. + //! + //! @param key The key to check. + //! @return true iff. the key is found, false otherwise. + [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool + { + return find(key) != cend(); + } + + //! Get a reference to the keys container. + //! + //! @return a reference to the keys container. + [[nodiscard]] constexpr auto keys() const noexcept -> key_container_type const & + { + return m_containers.keys; + } + + //! Get a reference to the values container. + //! + //! @return a reference to the values container. + [[nodiscard]] constexpr auto values() const noexcept -> mapped_container_type const & + { + return m_containers.values; + } + + private: + containers m_containers; + key_compare m_comparator; + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp index 9df03bb4..bfd02303 100644 --- a/libs/kstd/kstd/flat_map.test.cpp +++ b/libs/kstd/kstd/flat_map.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/libs/kstd/kstd/format b/libs/kstd/kstd/format deleted file mode 100644 index 5ed798db..00000000 --- a/libs/kstd/kstd/format +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef KSTD_FORMAT_HPP -#define KSTD_FORMAT_HPP - -// IWYU pragma: begin_exports -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// IWYU pragma: end_exports - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/format.hpp b/libs/kstd/kstd/format.hpp new file mode 100644 index 00000000..5ed798db --- /dev/null +++ b/libs/kstd/kstd/format.hpp @@ -0,0 +1,24 @@ +#ifndef KSTD_FORMAT_HPP +#define KSTD_FORMAT_HPP + +// IWYU pragma: begin_exports +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// IWYU pragma: end_exports + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp index 6126605f..d38a8107 100644 --- a/libs/kstd/kstd/format.test.cpp +++ b/libs/kstd/kstd/format.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/libs/kstd/kstd/libc/string.cpp b/libs/kstd/kstd/libc/string.cpp index b7cdb82e..15ecc70f 100644 --- a/libs/kstd/kstd/libc/string.cpp +++ b/libs/kstd/kstd/libc/string.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/libs/kstd/kstd/memory b/libs/kstd/kstd/memory deleted file mode 100644 index f108c6d6..00000000 --- a/libs/kstd/kstd/memory +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef KSTD_MEMORY_HPP -#define KSTD_MEMORY_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/memory.hpp b/libs/kstd/kstd/memory.hpp new file mode 100644 index 00000000..f108c6d6 --- /dev/null +++ b/libs/kstd/kstd/memory.hpp @@ -0,0 +1,8 @@ +#ifndef KSTD_MEMORY_HPP +#define KSTD_MEMORY_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/mutex b/libs/kstd/kstd/mutex deleted file mode 100644 index b2a31aa9..00000000 --- a/libs/kstd/kstd/mutex +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef KSTD_MUTEX_HPP -#define KSTD_MUTEX_HPP - -#include - -namespace kstd -{ - - //! A non-recursive mutex. - struct mutex - { - mutex(); - ~mutex(); - - mutex(mutex const &) = delete; - mutex(mutex &&) = delete; - - auto operator=(mutex const &) -> mutex & = delete; - auto operator=(mutex &&) -> mutex & = delete; - - //! Lock the mutex. - //! - //! @note This function blocks for as long as the mutex is not available. - auto lock() -> void; - - //! Try to lock the mutex. - //! - //! @note This function never blocks. - //! @return @p true iff. the mutex was successfully locked, @p false otherwise. - auto try_lock() -> bool; - - //! Unlock the mutex. - //! - //! @note The behavior is undefined if the mutex is not currently held by the thread unlocking it. - auto unlock() -> void; - - private: - std::atomic_flag m_locked{}; - }; - - //! A tag type to specify that a given @p lockable wrapper should adopt ownership of the @p lockable. - struct adopt_lock_t - { - explicit adopt_lock_t() = default; - } constexpr inline adopt_lock{}; - - //! A tag type to specify that a given @p lockable wrapper should defer locking the @p lockable. - struct defer_lock_t - { - explicit defer_lock_t() = default; - } constexpr inline defer_lock{}; - - //! A tag type to specify that a given @p lockable wrapper should attempt to lock the @p lockable. - struct try_to_lock_t - { - explicit try_to_lock_t() = default; - } constexpr inline try_to_lock{}; - - //! An RAII wrapper for a single @p lockable like a kstd::mutex. - template - struct lock_guard - { - using mutex_type = MutexType; - - //! Construct a new lock_guard and immediately lock the given mutex. - //! - //! @note This function will block until the mutex was successfully locked. - //! @param mutex The mutex to lock. - lock_guard(MutexType & mutex) noexcept - : m_mutex(mutex) - { - m_mutex.lock(); - } - - //! Construct a new lock_guard and take ownership of the given mutex. - //! - //! @note The behavior is undefined if the mutex is not owned by the current thread. - //! @param mutex The mutex to take ownership of. - lock_guard(MutexType & mutex, adopt_lock_t) noexcept - : m_mutex(mutex) - {} - - //! Destroy this lock_guard and release the owned mutex. - ~lock_guard() - { - m_mutex.unlock(); - } - - lock_guard(lock_guard const &) noexcept = delete; - lock_guard(lock_guard &&) noexcept = delete; - - auto operator=(lock_guard const &) noexcept -> lock_guard & = delete; - auto operator=(lock_guard &&) noexcept -> lock_guard & = delete; - - private: - mutex_type & m_mutex; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/mutex.cpp b/libs/kstd/kstd/mutex.cpp index 73876572..c1c5b190 100644 --- a/libs/kstd/kstd/mutex.cpp +++ b/libs/kstd/kstd/mutex.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/libs/kstd/kstd/mutex.hpp b/libs/kstd/kstd/mutex.hpp new file mode 100644 index 00000000..b2a31aa9 --- /dev/null +++ b/libs/kstd/kstd/mutex.hpp @@ -0,0 +1,101 @@ +#ifndef KSTD_MUTEX_HPP +#define KSTD_MUTEX_HPP + +#include + +namespace kstd +{ + + //! A non-recursive mutex. + struct mutex + { + mutex(); + ~mutex(); + + mutex(mutex const &) = delete; + mutex(mutex &&) = delete; + + auto operator=(mutex const &) -> mutex & = delete; + auto operator=(mutex &&) -> mutex & = delete; + + //! Lock the mutex. + //! + //! @note This function blocks for as long as the mutex is not available. + auto lock() -> void; + + //! Try to lock the mutex. + //! + //! @note This function never blocks. + //! @return @p true iff. the mutex was successfully locked, @p false otherwise. + auto try_lock() -> bool; + + //! Unlock the mutex. + //! + //! @note The behavior is undefined if the mutex is not currently held by the thread unlocking it. + auto unlock() -> void; + + private: + std::atomic_flag m_locked{}; + }; + + //! A tag type to specify that a given @p lockable wrapper should adopt ownership of the @p lockable. + struct adopt_lock_t + { + explicit adopt_lock_t() = default; + } constexpr inline adopt_lock{}; + + //! A tag type to specify that a given @p lockable wrapper should defer locking the @p lockable. + struct defer_lock_t + { + explicit defer_lock_t() = default; + } constexpr inline defer_lock{}; + + //! A tag type to specify that a given @p lockable wrapper should attempt to lock the @p lockable. + struct try_to_lock_t + { + explicit try_to_lock_t() = default; + } constexpr inline try_to_lock{}; + + //! An RAII wrapper for a single @p lockable like a kstd::mutex. + template + struct lock_guard + { + using mutex_type = MutexType; + + //! Construct a new lock_guard and immediately lock the given mutex. + //! + //! @note This function will block until the mutex was successfully locked. + //! @param mutex The mutex to lock. + lock_guard(MutexType & mutex) noexcept + : m_mutex(mutex) + { + m_mutex.lock(); + } + + //! Construct a new lock_guard and take ownership of the given mutex. + //! + //! @note The behavior is undefined if the mutex is not owned by the current thread. + //! @param mutex The mutex to take ownership of. + lock_guard(MutexType & mutex, adopt_lock_t) noexcept + : m_mutex(mutex) + {} + + //! Destroy this lock_guard and release the owned mutex. + ~lock_guard() + { + m_mutex.unlock(); + } + + lock_guard(lock_guard const &) noexcept = delete; + lock_guard(lock_guard &&) noexcept = delete; + + auto operator=(lock_guard const &) noexcept -> lock_guard & = delete; + auto operator=(lock_guard &&) noexcept -> lock_guard & = delete; + + private: + mutex_type & m_mutex; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/print b/libs/kstd/kstd/print deleted file mode 100644 index 1033f722..00000000 --- a/libs/kstd/kstd/print +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef KSTD_PRINT -#define KSTD_PRINT - -#include // IWYU pragma: export -#include -#include - -#include - -namespace kstd -{ - - //! @qualifier kernel-defined - //! Format the given string using the given arguments and print it to the currently active output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto print(kstd::format_string...> format, Args const &... args) -> void - { - auto const arg_store = kstd::make_format_args(args...); - os::vprint(print_sink::stdout, format.str_view, arg_store.args); - } - - //! @qualifier kernel-defined - //! Format the given error string using the given arguments and print it to the currently active output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void - { - auto const arg_store = kstd::make_format_args(args...); - os::vprint(sink, format.str_view, arg_store.args); - } - - //! @qualifier kernel-defined - //! Format the given string using the given arguments and print it, including a newline, to the currently active - //! output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto println(kstd::format_string...> format, Args &&... args) -> void - { - print(print_sink::stdout, format, std::forward(args)...); - print(print_sink::stdout, "\n"); - } - - //! @qualifier kernel-defined - //! Format the given error string using the given arguments and print it, including a newline, to the currently active - //! output device. - //! - //! @param format The format string - //! @param args The arguments - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto println(print_sink sink, kstd::format_string...> format, Args &&... args) -> void - { - print(sink, format, std::forward(args)...); - print(sink, "\n"); - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/print.hpp b/libs/kstd/kstd/print.hpp new file mode 100644 index 00000000..6d00d83f --- /dev/null +++ b/libs/kstd/kstd/print.hpp @@ -0,0 +1,69 @@ +#ifndef KSTD_PRINT +#define KSTD_PRINT + +#include // IWYU pragma: export +#include +#include + +#include + +namespace kstd +{ + + //! @qualifier kernel-defined + //! Format the given string using the given arguments and print it to the currently active output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto print(kstd::format_string...> format, Args const &... args) -> void + { + auto const arg_store = kstd::make_format_args(args...); + os::vprint(print_sink::stdout, format.str_view, arg_store.args); + } + + //! @qualifier kernel-defined + //! Format the given error string using the given arguments and print it to the currently active output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void + { + auto const arg_store = kstd::make_format_args(args...); + os::vprint(sink, format.str_view, arg_store.args); + } + + //! @qualifier kernel-defined + //! Format the given string using the given arguments and print it, including a newline, to the currently active + //! output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto println(kstd::format_string...> format, Args &&... args) -> void + { + print(print_sink::stdout, format, std::forward(args)...); + print(print_sink::stdout, "\n"); + } + + //! @qualifier kernel-defined + //! Format the given error string using the given arguments and print it, including a newline, to the currently active + //! output device. + //! + //! @param format The format string + //! @param args The arguments + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto println(print_sink sink, kstd::format_string...> format, Args &&... args) -> void + { + print(sink, format, std::forward(args)...); + print(sink, "\n"); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ranges b/libs/kstd/kstd/ranges deleted file mode 100644 index 78c3adbf..00000000 --- a/libs/kstd/kstd/ranges +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef KSTD_RANGES -#define KSTD_RANGES - -#include // IWYU pragma: export - -namespace kstd -{ -#if __glibcxx_ranges_to_container - using std::from_range; - using std::from_range_t; -#else - struct from_range_t - { - explicit from_range_t() = default; - }; - constexpr auto inline from_range = from_range_t{}; -#endif - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ranges.hpp b/libs/kstd/kstd/ranges.hpp new file mode 100644 index 00000000..78c3adbf --- /dev/null +++ b/libs/kstd/kstd/ranges.hpp @@ -0,0 +1,21 @@ +#ifndef KSTD_RANGES +#define KSTD_RANGES + +#include // IWYU pragma: export + +namespace kstd +{ +#if __glibcxx_ranges_to_container + using std::from_range; + using std::from_range_t; +#else + struct from_range_t + { + explicit from_range_t() = default; + }; + constexpr auto inline from_range = from_range_t{}; +#endif + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/stack b/libs/kstd/kstd/stack deleted file mode 100644 index 02e44ea4..00000000 --- a/libs/kstd/kstd/stack +++ /dev/null @@ -1,192 +0,0 @@ -#ifndef KSTD_STACK_HPP -#define KSTD_STACK_HPP - -#include - -#include -#include - -namespace kstd -{ - /** - * @brief Custom stack implementation mirroring the std::stack to allow for the usage of STL functionality with our - * custom memory management. - * - * @tparam T Element the stack instance should contain. - * @tparam Container Actual underlying container that should be wrapped to provide stack functionality. Requires - * access to pop_back(), push_back(), back(), size(), empty() and emplace_back() - */ - template> - struct stack - { - using container_type = Container; ///< Type of the underlying container used to implement stack-like interface. - using value_type = Container::value_type; ///< Type of the elements contained in the underlying container. - using size_type = Container::size_type; ///< Type of the size in the underlying container. - using reference = Container::reference; ///< Type of reference to the elements. - using const_reference = Container::const_reference; ///< Type of constant reference to the elements. - - /** - * @brief Default Constructor. - */ - stack() = default; - - stack(stack const &) = delete; - stack(stack &&) = delete; - auto operator=(stack const &) -> stack & = delete; - auto operator=(stack &&) -> stack & = delete; - /** - * @brief Constructs data with the given amount of elements containing the given value or alternatively the default - * constructed value. - * - * @param n Amount of elements we want to create and set the given value for. - * @param initial Inital value of all elements in the underlying data array. - */ - explicit stack(size_type n, value_type initial = value_type{}) - : _container(n, initial) - { - // Nothing to do. - } - - /** - * @brief Constructs data by copying all element from the given exclusive range. - * - * @tparam InputIterator Template that should have atleast input iterator characteristics. - * @param first Input iterator to the first element in the range we want to copy from. - * @param last Input iterator to one past the last element in the range we want to copy from. - */ - template - explicit stack(InputIterator first, InputIterator last) - : _container(first, last) - { - // Nothing to do. - } - - /** - * @brief Construct data by copying all elements from the initializer list. - * - * @param elements List we want to copy all elements from. - */ - explicit stack(std::initializer_list elements) - : _container(elements) - { - // Nothing to do. - } - - /** - * @brief Copy constructor. - * - * @note Allocates underlying data container with the same capacity as stack we are copying from and copies all - * elements from it. - * - * @param other Other instance of stack we want to copy the data from. - */ - stack(stack const & other) - : _container(other) - { - // Nothing to do. - } - - /** - * @brief Destructor. - */ - ~stack() = default; - - /** - * @brief Amount of elements currently contained in this vector, will fill up until we have reached the capacity. If - * that is the case the capacity is increased automatically. - * - * @return Current amount of elements. - */ - auto size() const -> size_type - { - return _container.size(); - } - - /** - * @brief Returns a reference to the last element in the container. Calling back on an empty container causes - * undefined behavior. - * - * @return Reference to the last element. - */ - auto top() -> reference - { - return _container.back(); - } - - /** - * @brief Returns a reference to the last element in the container. Calling back on an empty container causes - * undefined behavior. - * - * @return Reference to the last element. - */ - auto top() const -> const_reference - { - return _container.back(); - } - - /** - * @brief Appends the given element value to the end of the container. The element is assigned through the - * assignment operator of the template type. The value is forwarded to the constructor as - * std::forward(value), meaning it is either moved (rvalue) or copied (lvalue). - * - * @note If after the operation the new size() is greater than old capacity() a reallocation takes place, - * in which case all iterators (including the end() iterator) and all references to the elements are invalidated. - * Otherwise only the end() iterator is invalidated. Uses a forward reference for the actual value passed, which - * allows the template method to be used by both lvalue and rvalues and compile a different implementation. - * - * @param value The value of the element to append. - */ - template - auto push(U && value) -> void - { - _container.push_back(std::forward(value)); - } - - /** - * @brief Appends a new element to the end of the container. The element is constructed through a constructor of the - * template type. The arguments args... are forwarded to the constructor as std::forward(args).... - * - * If after the operation the new size() is greater than old capacity() a reallocation takes place, in which case - * all iterators (including the end() iterator) and all references to the elements are invalidated. Otherwise only - * the end() iterator is invalidated. Uses a forward reference for the actual value passed, which - * allows the template method to be used by both lvalue and rvalues and compile a different implementation. - * - * @tparam Args - * @param args Arguments to forward to the constructor of the element - * @return value_type& - */ - template - auto emplace(Args &&... args) -> reference - { - _container.emplace_back(std::forward(args)...); - } - - /** - * @brief Removes the last element of the container. - * - * @note Calling pop_back on an empty container results in halting the - * further execution. Iterators and references to the last element are invalidated. The end() - * iterator is also invalidated. - */ - auto pop() -> void - { - _container.pop_back(); - } - - /** - * @brief Whether there are currently any items this container or not. - * - * @return True if there are no elements, false if there are. - */ - auto empty() const -> bool - { - return _container.empty(); - } - - private: - container_type _container = {}; ///< Underlying container used by the stack to actually save the data. - }; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/stack.hpp b/libs/kstd/kstd/stack.hpp new file mode 100644 index 00000000..27e693f2 --- /dev/null +++ b/libs/kstd/kstd/stack.hpp @@ -0,0 +1,192 @@ +#ifndef KSTD_STACK_HPP +#define KSTD_STACK_HPP + +#include + +#include +#include + +namespace kstd +{ + /** + * @brief Custom stack implementation mirroring the std::stack to allow for the usage of STL functionality with our + * custom memory management. + * + * @tparam T Element the stack instance should contain. + * @tparam Container Actual underlying container that should be wrapped to provide stack functionality. Requires + * access to pop_back(), push_back(), back(), size(), empty() and emplace_back() + */ + template> + struct stack + { + using container_type = Container; ///< Type of the underlying container used to implement stack-like interface. + using value_type = Container::value_type; ///< Type of the elements contained in the underlying container. + using size_type = Container::size_type; ///< Type of the size in the underlying container. + using reference = Container::reference; ///< Type of reference to the elements. + using const_reference = Container::const_reference; ///< Type of constant reference to the elements. + + /** + * @brief Default Constructor. + */ + stack() = default; + + stack(stack const &) = delete; + stack(stack &&) = delete; + auto operator=(stack const &) -> stack & = delete; + auto operator=(stack &&) -> stack & = delete; + /** + * @brief Constructs data with the given amount of elements containing the given value or alternatively the default + * constructed value. + * + * @param n Amount of elements we want to create and set the given value for. + * @param initial Inital value of all elements in the underlying data array. + */ + explicit stack(size_type n, value_type initial = value_type{}) + : _container(n, initial) + { + // Nothing to do. + } + + /** + * @brief Constructs data by copying all element from the given exclusive range. + * + * @tparam InputIterator Template that should have atleast input iterator characteristics. + * @param first Input iterator to the first element in the range we want to copy from. + * @param last Input iterator to one past the last element in the range we want to copy from. + */ + template + explicit stack(InputIterator first, InputIterator last) + : _container(first, last) + { + // Nothing to do. + } + + /** + * @brief Construct data by copying all elements from the initializer list. + * + * @param elements List we want to copy all elements from. + */ + explicit stack(std::initializer_list elements) + : _container(elements) + { + // Nothing to do. + } + + /** + * @brief Copy constructor. + * + * @note Allocates underlying data container with the same capacity as stack we are copying from and copies all + * elements from it. + * + * @param other Other instance of stack we want to copy the data from. + */ + stack(stack const & other) + : _container(other) + { + // Nothing to do. + } + + /** + * @brief Destructor. + */ + ~stack() = default; + + /** + * @brief Amount of elements currently contained in this vector, will fill up until we have reached the capacity. If + * that is the case the capacity is increased automatically. + * + * @return Current amount of elements. + */ + auto size() const -> size_type + { + return _container.size(); + } + + /** + * @brief Returns a reference to the last element in the container. Calling back on an empty container causes + * undefined behavior. + * + * @return Reference to the last element. + */ + auto top() -> reference + { + return _container.back(); + } + + /** + * @brief Returns a reference to the last element in the container. Calling back on an empty container causes + * undefined behavior. + * + * @return Reference to the last element. + */ + auto top() const -> const_reference + { + return _container.back(); + } + + /** + * @brief Appends the given element value to the end of the container. The element is assigned through the + * assignment operator of the template type. The value is forwarded to the constructor as + * std::forward(value), meaning it is either moved (rvalue) or copied (lvalue). + * + * @note If after the operation the new size() is greater than old capacity() a reallocation takes place, + * in which case all iterators (including the end() iterator) and all references to the elements are invalidated. + * Otherwise only the end() iterator is invalidated. Uses a forward reference for the actual value passed, which + * allows the template method to be used by both lvalue and rvalues and compile a different implementation. + * + * @param value The value of the element to append. + */ + template + auto push(U && value) -> void + { + _container.push_back(std::forward(value)); + } + + /** + * @brief Appends a new element to the end of the container. The element is constructed through a constructor of the + * template type. The arguments args... are forwarded to the constructor as std::forward(args).... + * + * If after the operation the new size() is greater than old capacity() a reallocation takes place, in which case + * all iterators (including the end() iterator) and all references to the elements are invalidated. Otherwise only + * the end() iterator is invalidated. Uses a forward reference for the actual value passed, which + * allows the template method to be used by both lvalue and rvalues and compile a different implementation. + * + * @tparam Args + * @param args Arguments to forward to the constructor of the element + * @return value_type& + */ + template + auto emplace(Args &&... args) -> reference + { + _container.emplace_back(std::forward(args)...); + } + + /** + * @brief Removes the last element of the container. + * + * @note Calling pop_back on an empty container results in halting the + * further execution. Iterators and references to the last element are invalidated. The end() + * iterator is also invalidated. + */ + auto pop() -> void + { + _container.pop_back(); + } + + /** + * @brief Whether there are currently any items this container or not. + * + * @return True if there are no elements, false if there are. + */ + auto empty() const -> bool + { + return _container.empty(); + } + + private: + container_type _container = {}; ///< Underlying container used by the stack to actually save the data. + }; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string deleted file mode 100644 index 4a67a3a4..00000000 --- a/libs/kstd/kstd/string +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef KSTD_STRING_HPP -#define KSTD_STRING_HPP - -#include // IWYU pragma: export -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace kstd -{ - using string = basic_string; - - /** - * @brief Converts an integer to a string as if by kstd::format("{}", value). - * @tparam IntegralType The type of the unsigned integer to convert. - * @param value The unsigned integer to convert. - * @return A string representation of the given unsigned integer. - */ - template - [[nodiscard]] constexpr auto inline to_string(IntegralType value) -> string - { - return kstd::format("{}", value); - } - - template<> - struct formatter : formatter - { - auto format(string const & str, format_context & context) const -> void - { - formatter::format(str.data(), context); - } - }; - - inline namespace literals - { - inline namespace string_literals - { - //! Create a string new string from a literal C-style string. - //! - //! @param string The C-style string to use as a source for the new string. - //! @param length The length of the supplied C-style string. - //! @return a new string object, containing the same value as the provided C-style string. - constexpr auto operator""_s(char const * string, std::size_t length) -> kstd::string - { - return {string, length}; - } - } // namespace string_literals - } // namespace literals - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/kstd/string.hpp b/libs/kstd/kstd/string.hpp new file mode 100644 index 00000000..9978affc --- /dev/null +++ b/libs/kstd/kstd/string.hpp @@ -0,0 +1,60 @@ +#ifndef KSTD_STRING_HPP +#define KSTD_STRING_HPP + +#include // IWYU pragma: export +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + using string = basic_string; + + /** + * @brief Converts an integer to a string as if by kstd::format("{}", value). + * @tparam IntegralType The type of the unsigned integer to convert. + * @param value The unsigned integer to convert. + * @return A string representation of the given unsigned integer. + */ + template + [[nodiscard]] constexpr auto inline to_string(IntegralType value) -> string + { + return kstd::format("{}", value); + } + + template<> + struct formatter : formatter + { + auto format(string const & str, format_context & context) const -> void + { + formatter::format(str.data(), context); + } + }; + + inline namespace literals + { + inline namespace string_literals + { + //! Create a string new string from a literal C-style string. + //! + //! @param string The C-style string to use as a source for the new string. + //! @param length The length of the supplied C-style string. + //! @return a new string object, containing the same value as the provided C-style string. + constexpr auto operator""_s(char const * string, std::size_t length) -> kstd::string + { + return {string, length}; + } + } // namespace string_literals + } // namespace literals + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index e11877e5..b8fa1139 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -1,6 +1,6 @@ -#include +#include -#include +#include #include #include diff --git a/libs/kstd/kstd/units b/libs/kstd/kstd/units deleted file mode 100644 index df5eb374..00000000 --- a/libs/kstd/kstd/units +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef KSTD_UNITS_HPP -#define KSTD_UNITS_HPP - -#include -#include -#include - -namespace kstd -{ - - //! A basic template for strongly typed units. - template - struct basic_unit - { - using value_type = ValueType; - - constexpr basic_unit() noexcept - : value{} - {} - - explicit constexpr basic_unit(value_type value) noexcept - : value{value} - {} - - explicit constexpr operator value_type() const noexcept - { - return value; - } - - constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit - { - return basic_unit{value + other.value}; - } - - constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit & - { - return *this = *this + other; - } - - constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit - { - return basic_unit{value - other.value}; - } - - constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit & - { - return *this = *this - other; - } - - constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit - { - return basic_unit{value * factor}; - } - - constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit - { - return *this = *this * factor; - } - - constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit - { - return basic_unit{value / divisor}; - } - - constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit - { - return *this = *this / divisor; - } - - constexpr auto operator/(basic_unit const & other) const noexcept - { - return value / other.value; - } - - constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default; - - value_type value; - }; - - template - constexpr auto operator*(Factor factor, basic_unit const & unit) noexcept - -> basic_unit - { - return basic_unit{unit.value * factor}; - } - - namespace units - { - using bytes = basic_unit; - - constexpr auto KiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024}; - } - - constexpr auto MiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024 * 1024}; - } - - constexpr auto GiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024 * 1024 * 1024}; - } - - template - constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType * - { - return pointer + offset.value; - } - - } // namespace units - - namespace units_literals - { - constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes - { - return units::bytes{value}; - } - - constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes - { - return units::KiB(value); - } - - constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes - { - return units::MiB(value); - } - - constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes - { - return units::GiB(value); - } - - } // namespace units_literals - - template - constexpr auto object_size(ValueType const &) -> units::bytes - { - return units::bytes{sizeof(ValueType)}; - } - - template - constexpr auto type_size = units::bytes{sizeof(T)}; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/units.hpp b/libs/kstd/kstd/units.hpp new file mode 100644 index 00000000..df5eb374 --- /dev/null +++ b/libs/kstd/kstd/units.hpp @@ -0,0 +1,149 @@ +#ifndef KSTD_UNITS_HPP +#define KSTD_UNITS_HPP + +#include +#include +#include + +namespace kstd +{ + + //! A basic template for strongly typed units. + template + struct basic_unit + { + using value_type = ValueType; + + constexpr basic_unit() noexcept + : value{} + {} + + explicit constexpr basic_unit(value_type value) noexcept + : value{value} + {} + + explicit constexpr operator value_type() const noexcept + { + return value; + } + + constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit + { + return basic_unit{value + other.value}; + } + + constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit & + { + return *this = *this + other; + } + + constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit + { + return basic_unit{value - other.value}; + } + + constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit & + { + return *this = *this - other; + } + + constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit + { + return basic_unit{value * factor}; + } + + constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit + { + return *this = *this * factor; + } + + constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit + { + return basic_unit{value / divisor}; + } + + constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit + { + return *this = *this / divisor; + } + + constexpr auto operator/(basic_unit const & other) const noexcept + { + return value / other.value; + } + + constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default; + + value_type value; + }; + + template + constexpr auto operator*(Factor factor, basic_unit const & unit) noexcept + -> basic_unit + { + return basic_unit{unit.value * factor}; + } + + namespace units + { + using bytes = basic_unit; + + constexpr auto KiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024}; + } + + constexpr auto MiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024 * 1024}; + } + + constexpr auto GiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024 * 1024 * 1024}; + } + + template + constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType * + { + return pointer + offset.value; + } + + } // namespace units + + namespace units_literals + { + constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes + { + return units::bytes{value}; + } + + constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes + { + return units::KiB(value); + } + + constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes + { + return units::MiB(value); + } + + constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes + { + return units::GiB(value); + } + + } // namespace units_literals + + template + constexpr auto object_size(ValueType const &) -> units::bytes + { + return units::bytes{sizeof(ValueType)}; + } + + template + constexpr auto type_size = units::bytes{sizeof(T)}; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/vector b/libs/kstd/kstd/vector deleted file mode 100644 index 736b854d..00000000 --- a/libs/kstd/kstd/vector +++ /dev/null @@ -1,1079 +0,0 @@ -#ifndef KSTD_VECTOR_HPP -#define KSTD_VECTOR_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - //! A resizable, contiguous container. - //! - //! @tparam ValueType The type of values contained in this vector. - //! @tparam Allocator The type of allocator used for memory management by this container. - template> - struct vector - { - //! The type of the elements contained in this vector. - using value_type = ValueType; - //! The allocator used by this vector for memory management. - using allocator_type = Allocator; - //! The type of all sizes used in and with this vector. - using size_type = std::size_t; - //! The type of the difference between two iterators. - using difference_type = std::ptrdiff_t; - //! The type of references to elements in this vector. - using reference = value_type &; - //! The type of references to constant elements in this vector. - using const_reference = value_type const &; - //! The type of pointers to elements in this vector. - using pointer = std::allocator_traits::pointer; - //! The type of pointers to constant elements in this vector. - using const_pointer = std::allocator_traits::const_pointer; - //! The type of iterators into this container. - using iterator = pointer; - //! The type of constant iterators into this container. - using const_iterator = const_pointer; - //! The type of reverse iterators into this container. - using reverse_iterator = std::reverse_iterator; - //! The type of constant reverse iterators into this container. - using const_reverse_iterator = std::reverse_iterator; - - //! Construct a new, empty vector. - constexpr vector() noexcept(std::is_nothrow_default_constructible_v) - : vector(allocator_type{}) - {} - - //! Construct a new, empty vector with a given allocator. - //! - //! @param allocator The allocator to use in the vector. - explicit constexpr vector(allocator_type const & allocator) noexcept( - std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{0} - , m_capacity{0} - , m_data{allocate_n(m_capacity)} - {} - - //! Construct a new vector and fill it with the given number of default constructed elements. - //! - //! @param count The number of element to create the vector with. - //! @param allocator The allocator to use in the vector. - explicit constexpr vector(size_type count, allocator_type const & allocator = allocator_type{}) noexcept( - std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{count} - , m_capacity{count} - , m_data{allocate_n(m_capacity)} - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i); - } - } - - //! Construct a new vector and fill it with the given number of copy constructed elements. - //! - //! @param count The number of element to create the vector with. - //! @param value The value to copy for each element - //! @param allocator The allocator to use in the vector. - constexpr vector(size_type count, const_reference value, - allocator_type const & allocator = - allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{count} - , m_capacity{m_size} - , m_data{allocate_n(m_capacity)} - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, value); - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! @tparam ForwardIterator An iterator type used to describe the source range. - //! @param first The start of the source range. - //! @param last The end of the source range. - template - constexpr vector(ForwardIterator first, ForwardIterator last, - allocator_type const & allocator = - allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{static_cast(std::ranges::distance(first, last))} - , m_capacity{m_size} - , m_data{allocate_n(m_capacity)} - { - for (auto destination = m_data; first != last; ++first, ++destination) - { - std::allocator_traits::construct(m_allocator, destination, *first); - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! @tparam InputIterator An iterator type used to describe the source range. - //! @param first The start of the source range. - //! @param last The end of the source range. - template - constexpr vector(InputIterator first, InputIterator last, - allocator_type const & allocator = - allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{0} - , m_capacity{0} - , m_data{} - { - while (first != last) - { - emplace_back(*first); - ++first; - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! - template - requires((std::ranges::forward_range || std::ranges::sized_range) && - kstd::bits::container_compatible_range) - constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{}) - : m_allocator{allocator} - , m_size{std::ranges::size(range)} - , m_capacity{m_size} - , m_data{allocate_n(m_capacity)} - { - auto destination = m_data; - for (auto && element : std::forward(range)) - { - std::allocator_traits::construct(m_allocator, destination++, - std::forward>(element)); - } - } - - //! Construct a new vector and initialize it's content by copying all elements from a given vector. - //! - //! @param other The source vector. - constexpr vector(vector const & other) - : m_allocator{other.m_allocator} - , m_size{other.m_size} - , m_capacity{other.m_capacity} - , m_data(allocate_n(m_capacity)) - { - uninitialized_copy_with_allocator(other.begin(), begin(), other.size()); - } - - //! Construct a new vector and initialize it's content by moving from a given vector. - //! - //! @param other The source vector. - constexpr vector(vector && other) noexcept - : m_allocator{std::move(other.m_allocator)} - , m_size{std::exchange(other.m_size, size_type{})} - , m_capacity(std::exchange(other.m_capacity, size_type{})) - , m_data(std::exchange(other.m_data, nullptr)) - {} - - //! Construct a new vector and initialize it's content by copying from a given vector. - //! - //! @param other The source vector. - //! @param allocator The allocator to use in the vector. - constexpr vector(vector const & other, std::type_identity_t const & allocator) - : m_allocator{allocator} - , m_size{other.m_size} - , m_capacity{other.m_capacity} - , m_data{allocate_n(m_capacity)} - { - uninitialized_copy_with_allocator(other.begin(), begin(), other.size()); - } - - //! Construct a new vector and initialize it's content by copying from a given vector. - //! - //! @param other The source vector. - //! @param allocator The allocator to use in the vector. - constexpr vector(vector && other, std::type_identity_t const & allocator) - : m_allocator{allocator} - , m_size{} - , m_capacity{} - , m_data{} - { - if constexpr (!std::allocator_traits::is_always_equal::value) - { - if (m_allocator != other.m_allocator) - { - m_capacity = other.size(); - m_data = allocate_n(capacity()); - m_size = other.size(); - uninitialized_move_with_allocator(other.begin(), begin(), other.size()); - other.clear(); - return; - } - } - m_size = std::exchange(other.m_size, size_type{}); - m_capacity = std::exchange(other.m_capacity, size_type{}); - m_data = std::exchange(other.m_data, nullptr); - } - - //! Construct a new vector and initialize it's content by copying all elements in the given initializer list. - //! - //! @param list The initializer list containing the source objects. - vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) - : vector{std::ranges::begin(list), std::ranges::end(list), allocator} - {} - - //! Destroy this vector. - constexpr ~vector() - { - clear_and_deallocate(); - } - - //! Replace the contents of this vector by the copying from the given source vector. - //! - //! @param other The source vector. - constexpr auto operator=(vector const & other) -> vector & - { - if (this == std::addressof(other)) - { - return *this; - } - - if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) - { - if (get_allocator() != other.get_allocator()) - { - clear_and_deallocate(); - } - m_allocator = other.get_allocator(); - } - - if (capacity() >= other.size()) - { - auto const overlap = std::min(m_size, other.m_size); - std::ranges::copy(other.begin(), other.begin() + overlap, begin()); - - if (m_size < other.m_size) - { - uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); - } - else if (m_size > other.m_size) - { - destroy_n(begin() + other.size(), size() - other.size()); - } - } - else - { - auto new_data = allocate_n(other.size()); - uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = other.size(); - } - - m_size = other.size(); - return *this; - } - - //! Replace the contents fo this vector by moving from the given source. - //! - //! @param other The source vector. - constexpr auto operator=(vector && other) noexcept( - std::allocator_traits::propagate_on_container_move_assignment::value || - std::allocator_traits::is_always_equal::value) -> vector & - { - using std::swap; - - if (this == std::addressof(other)) - { - return *this; - } - - if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) - { - clear_and_deallocate(); - swap(m_allocator, other.m_allocator); - swap(m_size, other.m_size); - swap(m_capacity, other.m_capacity); - swap(m_data, other.m_data); - } - else if (m_allocator == other.m_allocator) - { - clear_and_deallocate(); - swap(m_size, other.m_size); - swap(m_capacity, other.m_capacity); - swap(m_data, other.m_data); - } - else - { - if (capacity() >= other.size()) - { - auto const overlap = std::min(size(), other.size()); - std::ranges::move(other.begin(), other.begin() + overlap, begin()); - - if (size() < other.size()) - { - uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); - } - else if (size() > other.size()) - { - destroy_n(begin() + other.size(), size() - other.size()); - } - } - else - { - auto new_data = allocate_n(other.size()); - uninitialized_move_with_allocator(other.begin(), new_data, other.size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = other.m_size; - } - - other.destroy_n(other.begin(), other.size()); - m_size = std::exchange(other.m_size, size_type{}); - } - - return *this; - } - - //! Get a copy of the allocator associated with this vector. - [[nodiscard]] constexpr auto get_allocator() const noexcept(std::is_nothrow_copy_constructible_v) - -> allocator_type - { - return m_allocator; - } - - //! Get a reference to the element at the given index. - //! - //! This function will panic if the index is out of bounds for this vector. - //! - //! @param index The index of the element to retrieve. - //! @return A reference to the element at the specified index. - [[nodiscard]] constexpr auto at(size_type index) -> reference - { - panic_if_out_of_bounds(index); - return (*this)[index]; - } - - //! Get a reference to the element at the given index. - //! - //! This function will panic if the index is out of bounds for this vector. - //! - //! @param index The index of the element to retrieve. - //! @return A reference to the element at the specified index. - [[nodiscard]] constexpr auto at(size_type index) const -> const_reference - { - panic_if_out_of_bounds(index); - return (*this)[index]; - } - - //! Get a reference to the element at the given index. - //! - //! The behavior is undefined if the index is out of bounds for this vector. - //! - //! @param index The index of the element to retrieve. - //! @return A reference to the element at the specified index. - [[nodiscard]] constexpr auto operator[](size_type index) -> reference - { - return data()[index]; - } - - //! Get a reference to the element at the given index. - //! - //! The behavior is undefined if the index is out of bounds for this vector. - //! - //! @param index The index of the element to retrieve. - //! @return A reference to the element at the specified index. - [[nodiscard]] constexpr auto operator[](size_type index) const -> const_reference - { - return data()[index]; - } - - //! Get a reference to the first element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto front() -> reference - { - return *begin(); - } - - //! Get a reference to the first element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto front() const -> const_reference - { - return *begin(); - } - - //! Get a reference to the last element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto back() -> reference - { - return *rbegin(); - } - - //! Get a reference to the last element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto back() const -> const_reference - { - return *rbegin(); - } - - //! Get a pointer to the beginning of the underlying contiguous storage. - [[nodiscard]] constexpr auto data() noexcept -> pointer - { - return m_data; - } - - //! Get a pointer to the beginning of the underlying contiguous storage. - [[nodiscard]] constexpr auto data() const noexcept -> const_pointer - { - return m_data; - } - - //! Get an iterator to the first element of this vector. - //! - //! @return An iterator to the first element of this container, or end() if the container is empty. - [[nodiscard]] constexpr auto begin() noexcept -> iterator - { - return empty() ? end() : data(); - } - - //! Get an iterator to the first element of this vector. - //! - //! @return An iterator to the first element of this container, or end() if the container is empty. - [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator - { - return empty() ? end() : data(); - } - - //! Get an iterator to the first element of this vector. - //! - //! @return An iterator to the first element of this container, or end() if the container is empty. - [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator - { - return begin(); - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto end() noexcept -> pointer - { - return capacity() ? data() + size() : nullptr; - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto end() const noexcept -> const_pointer - { - return capacity() ? data() + size() : nullptr; - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer - { - return end(); - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator - { - return empty() ? rend() : reverse_iterator{end()}; - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator - { - return empty() ? rend() : const_reverse_iterator{end()}; - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator - { - return rbegin(); - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator - { - return reverse_iterator{begin()}; - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{begin()}; - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator - { - return rend(); - } - - //! Check whether this vector is empty. - [[nodiscard]] constexpr auto empty() const noexcept -> bool - { - return !size(); - } - - //! Get the number of elements present in this vector. - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return m_size; - } - - //! Get the maximum possible number of element for this vector. - [[nodiscard]] constexpr auto max_size() const noexcept -> size_type - { - return std::allocator_traits::max_size(m_allocator); - } - - //! Reserve storage for at list the given number of elements. - constexpr auto reserve(size_type new_capacity) -> void - { - if (new_capacity <= capacity()) - { - return; - } - - if (new_capacity > max_size()) - { - kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible."); - } - - reallocate_exactly(new_capacity); - } - - //! Resize this vector to contain @p new_size elements. - constexpr auto resize(size_type new_size) -> void - { - resize(new_size, value_type{}); - } - - //! Resize this vector to contain @p new_size elements, filling new elements with @p value. - constexpr auto resize(size_type new_size, const_reference value) -> void - { - if (new_size < size()) - { - destroy_n(begin() + new_size, size() - new_size); - m_size = new_size; - return; - } - - if (new_size == size()) - { - return; - } - - if (new_size > max_size()) - { - kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible."); - } - - if (new_size > capacity()) - { - reserve(new_size); - } - - for (auto i = size(); i < new_size; ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, value); - } - - m_size = new_size; - } - - //! Get the number of element this vector has currently space for, including elements currently in this vector. - [[nodiscard]] constexpr auto capacity() const noexcept -> size_type - { - return m_capacity; - } - - //! Try to release unused storage space. - constexpr auto shrink_to_fit() -> void - { - if (m_size == m_capacity) - { - return; - } - - reallocate_exactly(m_size); - } - - //! Clear the contents of this vector. - constexpr auto clear() noexcept -> void - { - destroy_n(begin(), size()); - m_size = 0; - } - - //! Insert an element at a given position. - //! - //! @param position The position to insert the element at. - //! @param value The value to insert. - //! @return An iterator to the inserted element. - constexpr auto insert(const_iterator position, value_type const & value) -> iterator - { - return do_insert(position, value); - } - - //! Insert an element at a given position. - //! - //! @param position The position to insert the element at. - //! @param value The value to insert. - //! @return An iterator to the inserted element. - constexpr auto insert(const_iterator position, value_type && value) -> iterator - { - return do_insert(position, std::move(value)); - } - - //! Insert the element of a given range into the vector at a given position. - //! - //! @param range The source range to insert elements from. - //! @tparam SourceRange A container compatible range type. - template - requires requires(allocator_type allocator, pointer destination, SourceRange range) { - requires kstd::bits::container_compatible_range; - requires std::move_constructible; - requires std::is_move_assignable_v; - requires std::swappable; - std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); - } - // NOLINTNEXTLINE(misc-no-recursion) - constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator - { - auto prefix_size = std::ranges::distance(begin(), position); - - if (position == end()) - { - append_range(std::forward(range)); - return begin() + prefix_size; - } - - if constexpr (std::ranges::forward_range || std::ranges::sized_range) - { - auto number_of_elements = static_cast(std::ranges::distance(range)); - if (!number_of_elements) - { - return begin() + prefix_size; - } - - if (capacity() - size() < number_of_elements) - { - reserve(size() + std::max(size(), number_of_elements)); - } - - auto insert_position = begin() + prefix_size; - auto suffix_size = static_cast(std::ranges::distance(insert_position, end())); - - if (number_of_elements >= suffix_size) - { - uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size); - auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position); - uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size); - } - else - { - uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements); - std::ranges::move_backward(insert_position, end() - number_of_elements, end()); - std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position); - } - - m_size += number_of_elements; - return insert_position; - } - - auto range_begin = std::ranges::begin(range); - auto range_end = std::ranges::end(range); - - auto remainder = vector{get_allocator()}; - for (; range_begin != range_end; ++range_begin) - { - remainder.emplace_back(*static_cast>(range_begin)); - } - reserve(size() + std::max(size(), remainder.size())); - - return insert_range(begin() + prefix_size, remainder); - } - - template - constexpr auto emplace(const_iterator position, Args &&... args) -> iterator - { - auto prefix_size = std::ranges::distance(cbegin(), position); - if (position == cend()) - { - emplace_back(std::forward(args)...); - } - else if (m_capacity == m_size) - { - reallocate_and_insert(begin() + prefix_size, std::forward(args)...); - } - else - { - auto to_insert = value_type{std::forward(args)...}; - auto insert_position = begin() + prefix_size; - shift_back(insert_position); - *insert_position = std::move(to_insert); - ++m_size; - } - - return begin() + prefix_size; - } - - //! Erase an element at a given position. - //! - //! @note This function will panic if position == end() - //! - //! @param position An interator pointing to the element to delete - //! @return An iterator pointing to the element after the deleted element - constexpr auto erase(const_iterator position) -> iterator - { - if (position == end()) - { - os::panic("[kstd:vector] Attempted to erase end()!"); - } - - auto prefix_size = std::ranges::distance(cbegin(), position); - - std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size); - std::allocator_traits::destroy(m_allocator, end() - 1); - --m_size; - - return begin() + prefix_size; - } - - //! Erase a range of elements from this vector. - //! - //! @param first The start of the range to erase. - //! @param last The end of the range to erase. - //! @return An iterator pointing to the element after the last deleted element. - constexpr auto erase(const_iterator first, const_iterator last) -> iterator - { - if (first == last) - { - return begin() + std::ranges::distance(cbegin(), first); - } - - auto prefix_size = std::ranges::distance(cbegin(), first); - auto element_count = std::ranges::distance(first, last); - - std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size); - destroy_n(end() - element_count, element_count); - m_size -= element_count; - - return begin() + prefix_size; - } - - //! Append a given element to this vector via copy construction. - constexpr auto push_back(value_type const & value) -> void - { - emplace_back(value); - } - - //! Append a given element to this vector via move construction. - constexpr auto push_back(value_type && value) -> void - { - emplace_back(std::move(value)); - } - - //! Append a given element to this vector via direct construction. - template - constexpr auto emplace_back(Args &&... args) -> reference - { - if (m_capacity == m_size) - { - reallocate_and_insert(end(), std::forward(args)...); - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); - ++m_size; - } - return this->back(); - } - - //! Append the elements of a given range to this vector. - //! - //! @param range The range of elements to be appended. - //! @tparam SourceRange A container compatible range type. - template SourceRange> - requires requires(Allocator allocator, pointer destination, SourceRange range) { - std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); - } - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion) - constexpr auto append_range(SourceRange && range) -> void - { - if constexpr (std::ranges::forward_range || std::ranges::sized_range) - { - auto number_of_elements = static_cast(std::ranges::distance(range)); - - if (!capacity()) - { - reserve(number_of_elements); - } - - if (capacity() - size() >= number_of_elements) - { - uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements); - m_size += number_of_elements; - return; - } - - auto new_capacity = m_capacity + std::max(size(), number_of_elements); - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - - uninitialized_move_with_allocator(begin(), new_data, size()); - uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size + number_of_elements; - return; - } - - auto range_begin = std::ranges::begin(range); - auto range_end = std::ranges::end(range); - - for (auto i = capacity() - size(); i > 0; --i, ++range_begin) - { - emplace_back(*range_begin); - } - - if (range_begin == range_end) - { - return; - } - - auto remainder = vector{get_allocator()}; - for (; range_begin != range_end; ++range_begin) - { - remainder.emplace_back(*static_cast>(range_begin)); - } - reserve(size() + std::max(size(), remainder.size())); - append_range(remainder); - } - - //! Remove the last element of this vector. - //! - //! If this vector is empty, the behavior is undefined. - constexpr auto pop_back() -> void - { - --m_size; - std::allocator_traits::destroy(m_allocator, data() + size()); - } - - private: - //! Use the allocator of this vector to allocate enough space for the given number of elements. - //! - //! @param count The number of elements to allocate space for. - [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits::pointer - { - if (count) - { - return std::allocator_traits::allocate(m_allocator, count); - } - return nullptr; - } - - //! Clear this vector and release it's memory. - constexpr auto clear_and_deallocate() -> void - { - clear(); - deallocate(); - } - - //! Release the memory of this vector. - constexpr auto deallocate() - { - if (m_data) - { - std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); - m_capacity = 0; - m_size = 0; - m_data = nullptr; - } - } - - //! Insert an element into this vector at the given position. - //! - //! @param position The position to insert the element at. - //! @param value The value to insert. - template - constexpr auto do_insert(const_iterator position, U && value) - { - auto prefix_size = std::ranges::distance(cbegin(), position); - if (position == cend()) - { - push_back(std::forward(value)); - } - else if (m_capacity == m_size) - { - reallocate_and_insert(begin() + prefix_size, std::forward(value)); - } - else if (&value >= cbegin() && &value < cend()) - { - auto temporary = std::forward(value); - shift_back(begin() + prefix_size); - *(begin() + prefix_size) = std::move(temporary); - ++m_size; - } - else - { - shift_back(begin() + prefix_size); - *(begin() + prefix_size) = std::forward(value); - ++m_size; - } - return begin() + prefix_size; - } - - //! Destroy a number of elements in this vector. - //! - //! @param first The start of the range of the elements to be destroyed. - //! @param count The number of elements to destroy. - constexpr auto destroy_n(iterator first, std::size_t count) -> void - { - std::ranges::for_each(first, first + count, [&](auto & element) { - std::allocator_traits::destroy(m_allocator, std::addressof(element)); - }); - } - - //! Panic the kernel if the given index is out of bounds. - //! - //! @param index The index to check. - constexpr auto panic_if_out_of_bounds(size_type index) const -> void - { - if (index >= m_size) - { - os::panic("[kstd:vector] Attempted to read element at invalid index"); - } - } - - //! Copy a number of elements from a source range into the uninitialized destination range inside this vector. - //! - //! @param from The start of the source range. - //! @param to The start of the target range inside this vector. - //! @param count The number of elements to copy - template - constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count) - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, to++, *from++); - } - } - - //! Move a number of elements from a source range into the uninitialized destination range inside this vector. - //! - //! @param from The start of the source range. - //! @param to The start of the target range inside this vector. - //! @param count The number of elements to copy - template - constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count) - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); - } - } - - //! Reallocate the storage space to be exactly as large as the given size. - constexpr auto reallocate_exactly(size_type new_capacity) -> void - { - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, old_size); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - - //! Shift all elements, starting the given position, one position back inside the vector. - constexpr auto shift_back(iterator starting_at) - { - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(starting_at, end() - 1, end()); - } - - //! Reallocate the storage of this vector and insert an element at the given position. - //! - //! @param position The position to insert the element at. - //! @param args The constructor arguments for the inserted element. - template - constexpr auto reallocate_and_insert(iterator position, Args &&... args) - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - - std::allocator_traits::construct(m_allocator, new_data + prefix_size, - std::forward(args)...); - uninitialized_move_with_allocator(begin(), new_data, prefix_size); - uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); - destroy_n(begin(), old_size); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size + 1; - } - - //! The allocator used by this vector. - [[no_unique_address]] allocator_type m_allocator{}; - - //! The number of elements in this vector. - size_type m_size{}; - - //! The number of elements this vector has room for. - size_type m_capacity{}; - - //! The pointer to the start of the memory managed by this vector. - value_type * m_data{}; - }; - - //! Check if the content of two vectors is equal. - template - constexpr auto operator==(vector const & lhs, vector const & rhs) -> bool - { - return std::ranges::equal(lhs, rhs); - } - - //! Perform a lexicographical comparison of the content of two vectors. - template - constexpr auto operator<=>(vector const & lhs, vector const & rhs) - -> decltype(std::declval() <=> std::declval()) - { - return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); - } - - //! Deduction guide for vector construction from an interator pair. - template::value_type>> - vector(ForwardIterator, ForwardIterator, Allocator = Allocator()) - -> vector::value_type, Allocator>; - - //! Deduction guide for vector construction from an interator pair. - template::value_type>> - vector(InputIterator, InputIterator, Allocator = Allocator()) - -> vector::value_type, Allocator>; - - //! Deduction guide for vector construction from a range. - template>> - vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector, Allocator>; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/vector.hpp b/libs/kstd/kstd/vector.hpp new file mode 100644 index 00000000..7cc14283 --- /dev/null +++ b/libs/kstd/kstd/vector.hpp @@ -0,0 +1,1079 @@ +#ifndef KSTD_VECTOR_HPP +#define KSTD_VECTOR_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + //! A resizable, contiguous container. + //! + //! @tparam ValueType The type of values contained in this vector. + //! @tparam Allocator The type of allocator used for memory management by this container. + template> + struct vector + { + //! The type of the elements contained in this vector. + using value_type = ValueType; + //! The allocator used by this vector for memory management. + using allocator_type = Allocator; + //! The type of all sizes used in and with this vector. + using size_type = std::size_t; + //! The type of the difference between two iterators. + using difference_type = std::ptrdiff_t; + //! The type of references to elements in this vector. + using reference = value_type &; + //! The type of references to constant elements in this vector. + using const_reference = value_type const &; + //! The type of pointers to elements in this vector. + using pointer = std::allocator_traits::pointer; + //! The type of pointers to constant elements in this vector. + using const_pointer = std::allocator_traits::const_pointer; + //! The type of iterators into this container. + using iterator = pointer; + //! The type of constant iterators into this container. + using const_iterator = const_pointer; + //! The type of reverse iterators into this container. + using reverse_iterator = std::reverse_iterator; + //! The type of constant reverse iterators into this container. + using const_reverse_iterator = std::reverse_iterator; + + //! Construct a new, empty vector. + constexpr vector() noexcept(std::is_nothrow_default_constructible_v) + : vector(allocator_type{}) + {} + + //! Construct a new, empty vector with a given allocator. + //! + //! @param allocator The allocator to use in the vector. + explicit constexpr vector(allocator_type const & allocator) noexcept( + std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{0} + , m_capacity{0} + , m_data{allocate_n(m_capacity)} + {} + + //! Construct a new vector and fill it with the given number of default constructed elements. + //! + //! @param count The number of element to create the vector with. + //! @param allocator The allocator to use in the vector. + explicit constexpr vector(size_type count, allocator_type const & allocator = allocator_type{}) noexcept( + std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{count} + , m_capacity{count} + , m_data{allocate_n(m_capacity)} + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i); + } + } + + //! Construct a new vector and fill it with the given number of copy constructed elements. + //! + //! @param count The number of element to create the vector with. + //! @param value The value to copy for each element + //! @param allocator The allocator to use in the vector. + constexpr vector(size_type count, const_reference value, + allocator_type const & allocator = + allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{count} + , m_capacity{m_size} + , m_data{allocate_n(m_capacity)} + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, value); + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! @tparam ForwardIterator An iterator type used to describe the source range. + //! @param first The start of the source range. + //! @param last The end of the source range. + template + constexpr vector(ForwardIterator first, ForwardIterator last, + allocator_type const & allocator = + allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{static_cast(std::ranges::distance(first, last))} + , m_capacity{m_size} + , m_data{allocate_n(m_capacity)} + { + for (auto destination = m_data; first != last; ++first, ++destination) + { + std::allocator_traits::construct(m_allocator, destination, *first); + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! @tparam InputIterator An iterator type used to describe the source range. + //! @param first The start of the source range. + //! @param last The end of the source range. + template + constexpr vector(InputIterator first, InputIterator last, + allocator_type const & allocator = + allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{0} + , m_capacity{0} + , m_data{} + { + while (first != last) + { + emplace_back(*first); + ++first; + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! + template + requires((std::ranges::forward_range || std::ranges::sized_range) && + kstd::bits::container_compatible_range) + constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{}) + : m_allocator{allocator} + , m_size{std::ranges::size(range)} + , m_capacity{m_size} + , m_data{allocate_n(m_capacity)} + { + auto destination = m_data; + for (auto && element : std::forward(range)) + { + std::allocator_traits::construct(m_allocator, destination++, + std::forward>(element)); + } + } + + //! Construct a new vector and initialize it's content by copying all elements from a given vector. + //! + //! @param other The source vector. + constexpr vector(vector const & other) + : m_allocator{other.m_allocator} + , m_size{other.m_size} + , m_capacity{other.m_capacity} + , m_data(allocate_n(m_capacity)) + { + uninitialized_copy_with_allocator(other.begin(), begin(), other.size()); + } + + //! Construct a new vector and initialize it's content by moving from a given vector. + //! + //! @param other The source vector. + constexpr vector(vector && other) noexcept + : m_allocator{std::move(other.m_allocator)} + , m_size{std::exchange(other.m_size, size_type{})} + , m_capacity(std::exchange(other.m_capacity, size_type{})) + , m_data(std::exchange(other.m_data, nullptr)) + {} + + //! Construct a new vector and initialize it's content by copying from a given vector. + //! + //! @param other The source vector. + //! @param allocator The allocator to use in the vector. + constexpr vector(vector const & other, std::type_identity_t const & allocator) + : m_allocator{allocator} + , m_size{other.m_size} + , m_capacity{other.m_capacity} + , m_data{allocate_n(m_capacity)} + { + uninitialized_copy_with_allocator(other.begin(), begin(), other.size()); + } + + //! Construct a new vector and initialize it's content by copying from a given vector. + //! + //! @param other The source vector. + //! @param allocator The allocator to use in the vector. + constexpr vector(vector && other, std::type_identity_t const & allocator) + : m_allocator{allocator} + , m_size{} + , m_capacity{} + , m_data{} + { + if constexpr (!std::allocator_traits::is_always_equal::value) + { + if (m_allocator != other.m_allocator) + { + m_capacity = other.size(); + m_data = allocate_n(capacity()); + m_size = other.size(); + uninitialized_move_with_allocator(other.begin(), begin(), other.size()); + other.clear(); + return; + } + } + m_size = std::exchange(other.m_size, size_type{}); + m_capacity = std::exchange(other.m_capacity, size_type{}); + m_data = std::exchange(other.m_data, nullptr); + } + + //! Construct a new vector and initialize it's content by copying all elements in the given initializer list. + //! + //! @param list The initializer list containing the source objects. + vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) + : vector{std::ranges::begin(list), std::ranges::end(list), allocator} + {} + + //! Destroy this vector. + constexpr ~vector() + { + clear_and_deallocate(); + } + + //! Replace the contents of this vector by the copying from the given source vector. + //! + //! @param other The source vector. + constexpr auto operator=(vector const & other) -> vector & + { + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) + { + if (get_allocator() != other.get_allocator()) + { + clear_and_deallocate(); + } + m_allocator = other.get_allocator(); + } + + if (capacity() >= other.size()) + { + auto const overlap = std::min(m_size, other.m_size); + std::ranges::copy(other.begin(), other.begin() + overlap, begin()); + + if (m_size < other.m_size) + { + uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); + } + else if (m_size > other.m_size) + { + destroy_n(begin() + other.size(), size() - other.size()); + } + } + else + { + auto new_data = allocate_n(other.size()); + uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = other.size(); + } + + m_size = other.size(); + return *this; + } + + //! Replace the contents fo this vector by moving from the given source. + //! + //! @param other The source vector. + constexpr auto operator=(vector && other) noexcept( + std::allocator_traits::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) -> vector & + { + using std::swap; + + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + clear_and_deallocate(); + swap(m_allocator, other.m_allocator); + swap(m_size, other.m_size); + swap(m_capacity, other.m_capacity); + swap(m_data, other.m_data); + } + else if (m_allocator == other.m_allocator) + { + clear_and_deallocate(); + swap(m_size, other.m_size); + swap(m_capacity, other.m_capacity); + swap(m_data, other.m_data); + } + else + { + if (capacity() >= other.size()) + { + auto const overlap = std::min(size(), other.size()); + std::ranges::move(other.begin(), other.begin() + overlap, begin()); + + if (size() < other.size()) + { + uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); + } + else if (size() > other.size()) + { + destroy_n(begin() + other.size(), size() - other.size()); + } + } + else + { + auto new_data = allocate_n(other.size()); + uninitialized_move_with_allocator(other.begin(), new_data, other.size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = other.m_size; + } + + other.destroy_n(other.begin(), other.size()); + m_size = std::exchange(other.m_size, size_type{}); + } + + return *this; + } + + //! Get a copy of the allocator associated with this vector. + [[nodiscard]] constexpr auto get_allocator() const noexcept(std::is_nothrow_copy_constructible_v) + -> allocator_type + { + return m_allocator; + } + + //! Get a reference to the element at the given index. + //! + //! This function will panic if the index is out of bounds for this vector. + //! + //! @param index The index of the element to retrieve. + //! @return A reference to the element at the specified index. + [[nodiscard]] constexpr auto at(size_type index) -> reference + { + panic_if_out_of_bounds(index); + return (*this)[index]; + } + + //! Get a reference to the element at the given index. + //! + //! This function will panic if the index is out of bounds for this vector. + //! + //! @param index The index of the element to retrieve. + //! @return A reference to the element at the specified index. + [[nodiscard]] constexpr auto at(size_type index) const -> const_reference + { + panic_if_out_of_bounds(index); + return (*this)[index]; + } + + //! Get a reference to the element at the given index. + //! + //! The behavior is undefined if the index is out of bounds for this vector. + //! + //! @param index The index of the element to retrieve. + //! @return A reference to the element at the specified index. + [[nodiscard]] constexpr auto operator[](size_type index) -> reference + { + return data()[index]; + } + + //! Get a reference to the element at the given index. + //! + //! The behavior is undefined if the index is out of bounds for this vector. + //! + //! @param index The index of the element to retrieve. + //! @return A reference to the element at the specified index. + [[nodiscard]] constexpr auto operator[](size_type index) const -> const_reference + { + return data()[index]; + } + + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() -> reference + { + return *begin(); + } + + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() const -> const_reference + { + return *begin(); + } + + //! Get a reference to the last element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto back() -> reference + { + return *rbegin(); + } + + //! Get a reference to the last element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto back() const -> const_reference + { + return *rbegin(); + } + + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer + { + return m_data; + } + + //! Get an iterator to the first element of this vector. + //! + //! @return An iterator to the first element of this container, or end() if the container is empty. + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return empty() ? end() : data(); + } + + //! Get an iterator to the first element of this vector. + //! + //! @return An iterator to the first element of this container, or end() if the container is empty. + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return empty() ? end() : data(); + } + + //! Get an iterator to the first element of this vector. + //! + //! @return An iterator to the first element of this container, or end() if the container is empty. + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() noexcept -> pointer + { + return capacity() ? data() + size() : nullptr; + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() const noexcept -> const_pointer + { + return capacity() ? data() + size() : nullptr; + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer + { + return end(); + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator + { + return empty() ? rend() : reverse_iterator{end()}; + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator + { + return empty() ? rend() : const_reverse_iterator{end()}; + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator + { + return rbegin(); + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator + { + return reverse_iterator{begin()}; + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{begin()}; + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator + { + return rend(); + } + + //! Check whether this vector is empty. + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return !size(); + } + + //! Get the number of elements present in this vector. + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_size; + } + + //! Get the maximum possible number of element for this vector. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type + { + return std::allocator_traits::max_size(m_allocator); + } + + //! Reserve storage for at list the given number of elements. + constexpr auto reserve(size_type new_capacity) -> void + { + if (new_capacity <= capacity()) + { + return; + } + + if (new_capacity > max_size()) + { + kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible."); + } + + reallocate_exactly(new_capacity); + } + + //! Resize this vector to contain @p new_size elements. + constexpr auto resize(size_type new_size) -> void + { + resize(new_size, value_type{}); + } + + //! Resize this vector to contain @p new_size elements, filling new elements with @p value. + constexpr auto resize(size_type new_size, const_reference value) -> void + { + if (new_size < size()) + { + destroy_n(begin() + new_size, size() - new_size); + m_size = new_size; + return; + } + + if (new_size == size()) + { + return; + } + + if (new_size > max_size()) + { + kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible."); + } + + if (new_size > capacity()) + { + reserve(new_size); + } + + for (auto i = size(); i < new_size; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, value); + } + + m_size = new_size; + } + + //! Get the number of element this vector has currently space for, including elements currently in this vector. + [[nodiscard]] constexpr auto capacity() const noexcept -> size_type + { + return m_capacity; + } + + //! Try to release unused storage space. + constexpr auto shrink_to_fit() -> void + { + if (m_size == m_capacity) + { + return; + } + + reallocate_exactly(m_size); + } + + //! Clear the contents of this vector. + constexpr auto clear() noexcept -> void + { + destroy_n(begin(), size()); + m_size = 0; + } + + //! Insert an element at a given position. + //! + //! @param position The position to insert the element at. + //! @param value The value to insert. + //! @return An iterator to the inserted element. + constexpr auto insert(const_iterator position, value_type const & value) -> iterator + { + return do_insert(position, value); + } + + //! Insert an element at a given position. + //! + //! @param position The position to insert the element at. + //! @param value The value to insert. + //! @return An iterator to the inserted element. + constexpr auto insert(const_iterator position, value_type && value) -> iterator + { + return do_insert(position, std::move(value)); + } + + //! Insert the element of a given range into the vector at a given position. + //! + //! @param range The source range to insert elements from. + //! @tparam SourceRange A container compatible range type. + template + requires requires(allocator_type allocator, pointer destination, SourceRange range) { + requires kstd::bits::container_compatible_range; + requires std::move_constructible; + requires std::is_move_assignable_v; + requires std::swappable; + std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); + } + // NOLINTNEXTLINE(misc-no-recursion) + constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + + if (position == end()) + { + append_range(std::forward(range)); + return begin() + prefix_size; + } + + if constexpr (std::ranges::forward_range || std::ranges::sized_range) + { + auto number_of_elements = static_cast(std::ranges::distance(range)); + if (!number_of_elements) + { + return begin() + prefix_size; + } + + if (capacity() - size() < number_of_elements) + { + reserve(size() + std::max(size(), number_of_elements)); + } + + auto insert_position = begin() + prefix_size; + auto suffix_size = static_cast(std::ranges::distance(insert_position, end())); + + if (number_of_elements >= suffix_size) + { + uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size); + auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position); + uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size); + } + else + { + uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements); + std::ranges::move_backward(insert_position, end() - number_of_elements, end()); + std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position); + } + + m_size += number_of_elements; + return insert_position; + } + + auto range_begin = std::ranges::begin(range); + auto range_end = std::ranges::end(range); + + auto remainder = vector{get_allocator()}; + for (; range_begin != range_end; ++range_begin) + { + remainder.emplace_back(*static_cast>(range_begin)); + } + reserve(size() + std::max(size(), remainder.size())); + + return insert_range(begin() + prefix_size, remainder); + } + + template + constexpr auto emplace(const_iterator position, Args &&... args) -> iterator + { + auto prefix_size = std::ranges::distance(cbegin(), position); + if (position == cend()) + { + emplace_back(std::forward(args)...); + } + else if (m_capacity == m_size) + { + reallocate_and_insert(begin() + prefix_size, std::forward(args)...); + } + else + { + auto to_insert = value_type{std::forward(args)...}; + auto insert_position = begin() + prefix_size; + shift_back(insert_position); + *insert_position = std::move(to_insert); + ++m_size; + } + + return begin() + prefix_size; + } + + //! Erase an element at a given position. + //! + //! @note This function will panic if position == end() + //! + //! @param position An interator pointing to the element to delete + //! @return An iterator pointing to the element after the deleted element + constexpr auto erase(const_iterator position) -> iterator + { + if (position == end()) + { + os::panic("[kstd:vector] Attempted to erase end()!"); + } + + auto prefix_size = std::ranges::distance(cbegin(), position); + + std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size); + std::allocator_traits::destroy(m_allocator, end() - 1); + --m_size; + + return begin() + prefix_size; + } + + //! Erase a range of elements from this vector. + //! + //! @param first The start of the range to erase. + //! @param last The end of the range to erase. + //! @return An iterator pointing to the element after the last deleted element. + constexpr auto erase(const_iterator first, const_iterator last) -> iterator + { + if (first == last) + { + return begin() + std::ranges::distance(cbegin(), first); + } + + auto prefix_size = std::ranges::distance(cbegin(), first); + auto element_count = std::ranges::distance(first, last); + + std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size); + destroy_n(end() - element_count, element_count); + m_size -= element_count; + + return begin() + prefix_size; + } + + //! Append a given element to this vector via copy construction. + constexpr auto push_back(value_type const & value) -> void + { + emplace_back(value); + } + + //! Append a given element to this vector via move construction. + constexpr auto push_back(value_type && value) -> void + { + emplace_back(std::move(value)); + } + + //! Append a given element to this vector via direct construction. + template + constexpr auto emplace_back(Args &&... args) -> reference + { + if (m_capacity == m_size) + { + reallocate_and_insert(end(), std::forward(args)...); + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); + ++m_size; + } + return this->back(); + } + + //! Append the elements of a given range to this vector. + //! + //! @param range The range of elements to be appended. + //! @tparam SourceRange A container compatible range type. + template SourceRange> + requires requires(Allocator allocator, pointer destination, SourceRange range) { + std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); + } + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion) + constexpr auto append_range(SourceRange && range) -> void + { + if constexpr (std::ranges::forward_range || std::ranges::sized_range) + { + auto number_of_elements = static_cast(std::ranges::distance(range)); + + if (!capacity()) + { + reserve(number_of_elements); + } + + if (capacity() - size() >= number_of_elements) + { + uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements); + m_size += number_of_elements; + return; + } + + auto new_capacity = m_capacity + std::max(size(), number_of_elements); + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + + uninitialized_move_with_allocator(begin(), new_data, size()); + uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements); + clear_and_deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size + number_of_elements; + return; + } + + auto range_begin = std::ranges::begin(range); + auto range_end = std::ranges::end(range); + + for (auto i = capacity() - size(); i > 0; --i, ++range_begin) + { + emplace_back(*range_begin); + } + + if (range_begin == range_end) + { + return; + } + + auto remainder = vector{get_allocator()}; + for (; range_begin != range_end; ++range_begin) + { + remainder.emplace_back(*static_cast>(range_begin)); + } + reserve(size() + std::max(size(), remainder.size())); + append_range(remainder); + } + + //! Remove the last element of this vector. + //! + //! If this vector is empty, the behavior is undefined. + constexpr auto pop_back() -> void + { + --m_size; + std::allocator_traits::destroy(m_allocator, data() + size()); + } + + private: + //! Use the allocator of this vector to allocate enough space for the given number of elements. + //! + //! @param count The number of elements to allocate space for. + [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits::pointer + { + if (count) + { + return std::allocator_traits::allocate(m_allocator, count); + } + return nullptr; + } + + //! Clear this vector and release it's memory. + constexpr auto clear_and_deallocate() -> void + { + clear(); + deallocate(); + } + + //! Release the memory of this vector. + constexpr auto deallocate() + { + if (m_data) + { + std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); + m_capacity = 0; + m_size = 0; + m_data = nullptr; + } + } + + //! Insert an element into this vector at the given position. + //! + //! @param position The position to insert the element at. + //! @param value The value to insert. + template + constexpr auto do_insert(const_iterator position, U && value) + { + auto prefix_size = std::ranges::distance(cbegin(), position); + if (position == cend()) + { + push_back(std::forward(value)); + } + else if (m_capacity == m_size) + { + reallocate_and_insert(begin() + prefix_size, std::forward(value)); + } + else if (&value >= cbegin() && &value < cend()) + { + auto temporary = std::forward(value); + shift_back(begin() + prefix_size); + *(begin() + prefix_size) = std::move(temporary); + ++m_size; + } + else + { + shift_back(begin() + prefix_size); + *(begin() + prefix_size) = std::forward(value); + ++m_size; + } + return begin() + prefix_size; + } + + //! Destroy a number of elements in this vector. + //! + //! @param first The start of the range of the elements to be destroyed. + //! @param count The number of elements to destroy. + constexpr auto destroy_n(iterator first, std::size_t count) -> void + { + std::ranges::for_each(first, first + count, [&](auto & element) { + std::allocator_traits::destroy(m_allocator, std::addressof(element)); + }); + } + + //! Panic the kernel if the given index is out of bounds. + //! + //! @param index The index to check. + constexpr auto panic_if_out_of_bounds(size_type index) const -> void + { + if (index >= m_size) + { + os::panic("[kstd:vector] Attempted to read element at invalid index"); + } + } + + //! Copy a number of elements from a source range into the uninitialized destination range inside this vector. + //! + //! @param from The start of the source range. + //! @param to The start of the target range inside this vector. + //! @param count The number of elements to copy + template + constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count) + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, to++, *from++); + } + } + + //! Move a number of elements from a source range into the uninitialized destination range inside this vector. + //! + //! @param from The start of the source range. + //! @param to The start of the target range inside this vector. + //! @param count The number of elements to copy + template + constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count) + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); + } + } + + //! Reallocate the storage space to be exactly as large as the given size. + constexpr auto reallocate_exactly(size_type new_capacity) -> void + { + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, old_size); + clear_and_deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + + //! Shift all elements, starting the given position, one position back inside the vector. + constexpr auto shift_back(iterator starting_at) + { + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(starting_at, end() - 1, end()); + } + + //! Reallocate the storage of this vector and insert an element at the given position. + //! + //! @param position The position to insert the element at. + //! @param args The constructor arguments for the inserted element. + template + constexpr auto reallocate_and_insert(iterator position, Args &&... args) + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + + std::allocator_traits::construct(m_allocator, new_data + prefix_size, + std::forward(args)...); + uninitialized_move_with_allocator(begin(), new_data, prefix_size); + uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); + destroy_n(begin(), old_size); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size + 1; + } + + //! The allocator used by this vector. + [[no_unique_address]] allocator_type m_allocator{}; + + //! The number of elements in this vector. + size_type m_size{}; + + //! The number of elements this vector has room for. + size_type m_capacity{}; + + //! The pointer to the start of the memory managed by this vector. + value_type * m_data{}; + }; + + //! Check if the content of two vectors is equal. + template + constexpr auto operator==(vector const & lhs, vector const & rhs) -> bool + { + return std::ranges::equal(lhs, rhs); + } + + //! Perform a lexicographical comparison of the content of two vectors. + template + constexpr auto operator<=>(vector const & lhs, vector const & rhs) + -> decltype(std::declval() <=> std::declval()) + { + return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + } + + //! Deduction guide for vector construction from an interator pair. + template::value_type>> + vector(ForwardIterator, ForwardIterator, Allocator = Allocator()) + -> vector::value_type, Allocator>; + + //! Deduction guide for vector construction from an interator pair. + template::value_type>> + vector(InputIterator, InputIterator, Allocator = Allocator()) + -> vector::value_type, Allocator>; + + //! Deduction guide for vector construction from a range. + template>> + vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector, Allocator>; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp index 8bf8f79d..72c1127a 100644 --- a/libs/kstd/kstd/vector.test.cpp +++ b/libs/kstd/kstd/vector.test.cpp @@ -1,6 +1,6 @@ -#include +#include -#include +#include #include #include diff --git a/libs/multiboot2/multiboot2/information.hpp b/libs/multiboot2/multiboot2/information.hpp index f688fe5d..f12969e8 100644 --- a/libs/multiboot2/multiboot2/information.hpp +++ b/libs/multiboot2/multiboot2/information.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include // IWYU pragma: export #include // IWYU pragma: export diff --git a/libs/multiboot2/multiboot2/information/data.hpp b/libs/multiboot2/multiboot2/information/data.hpp index f39a6cb2..734bbc6f 100644 --- a/libs/multiboot2/multiboot2/information/data.hpp +++ b/libs/multiboot2/multiboot2/information/data.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include +#include #include #include -- cgit v1.2.3 From 14027b42ef30075dedbe4f08328da65e6cc308a9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 13:58:10 +0200 Subject: kstd: silence documentation warnings --- libs/kstd/kstd/print.hpp | 2 ++ libs/kstd/kstd/vector.hpp | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/print.hpp b/libs/kstd/kstd/print.hpp index 6d00d83f..0d37efbe 100644 --- a/libs/kstd/kstd/print.hpp +++ b/libs/kstd/kstd/print.hpp @@ -26,6 +26,7 @@ namespace kstd //! @qualifier kernel-defined //! Format the given error string using the given arguments and print it to the currently active output device. //! + //! @param sink The output sink to print to. //! @param format The format string //! @param args The arguments to use to place in the format string's placeholders. template @@ -54,6 +55,7 @@ namespace kstd //! Format the given error string using the given arguments and print it, including a newline, to the currently active //! output device. //! + //! @param sink The output sink to print to. //! @param format The format string //! @param args The arguments template diff --git a/libs/kstd/kstd/vector.hpp b/libs/kstd/kstd/vector.hpp index 7cc14283..d6ad232a 100644 --- a/libs/kstd/kstd/vector.hpp +++ b/libs/kstd/kstd/vector.hpp @@ -107,6 +107,7 @@ namespace kstd //! @tparam ForwardIterator An iterator type used to describe the source range. //! @param first The start of the source range. //! @param last The end of the source range. + //! @param allocator The allocator to use in the vector. template constexpr vector(ForwardIterator first, ForwardIterator last, allocator_type const & allocator = @@ -127,6 +128,7 @@ namespace kstd //! @tparam InputIterator An iterator type used to describe the source range. //! @param first The start of the source range. //! @param last The end of the source range. + //! @param allocator The allocator to use in the vector. template constexpr vector(InputIterator first, InputIterator last, allocator_type const & allocator = @@ -228,6 +230,7 @@ namespace kstd //! Construct a new vector and initialize it's content by copying all elements in the given initializer list. //! //! @param list The initializer list containing the source objects. + //! @param allocator The allocator to use in the vector. vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) : vector{std::ranges::begin(list), std::ranges::end(list), allocator} {} @@ -639,6 +642,7 @@ namespace kstd //! Insert the element of a given range into the vector at a given position. //! + //! @param position The position where to insert the elements. //! @param range The source range to insert elements from. //! @tparam SourceRange A container compatible range type. template -- cgit v1.2.3 From 0c2d88cd36876df5eeb4ada2f9d339ce45b575be Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 14:09:57 +0200 Subject: chore: rename test source files --- libs/acpi/CMakeLists.txt | 10 +- libs/acpi/acpi/common/table_header.test.cpp | 60 - libs/acpi/acpi/common/table_header.tests.cpp | 60 + libs/acpi/acpi/data/madt.test.cpp | 57 - libs/acpi/acpi/data/madt.tests.cpp | 57 + libs/acpi/acpi/data/rsdt.test.cpp | 48 - libs/acpi/acpi/data/rsdt.tests.cpp | 48 + libs/acpi/acpi/data/xsdt.test.cpp | 48 - libs/acpi/acpi/data/xsdt.tests.cpp | 48 + libs/acpi/acpi/pointers.test.cpp | 30 - libs/acpi/acpi/pointers.tests.cpp | 30 + libs/kstd/CMakeLists.txt | 12 +- libs/kstd/kstd/bits/observer_ptr.test.cpp | 360 --- libs/kstd/kstd/bits/observer_ptr.tests.cpp | 360 +++ libs/kstd/kstd/flat_map.test.cpp | 351 --- libs/kstd/kstd/flat_map.tests.cpp | 351 +++ libs/kstd/kstd/format.test.cpp | 170 -- libs/kstd/kstd/format.tests.cpp | 170 ++ libs/kstd/kstd/string.test.cpp | 3166 ------------------------ libs/kstd/kstd/string.tests.cpp | 3166 ++++++++++++++++++++++++ libs/kstd/kstd/test_support/os_panic.test.cpp | 15 - libs/kstd/kstd/test_support/os_panic.tests.cpp | 15 + libs/kstd/kstd/vector.test.cpp | 2003 --------------- libs/kstd/kstd/vector.tests.cpp | 2003 +++++++++++++++ 24 files changed, 6319 insertions(+), 6319 deletions(-) delete mode 100644 libs/acpi/acpi/common/table_header.test.cpp create mode 100644 libs/acpi/acpi/common/table_header.tests.cpp delete mode 100644 libs/acpi/acpi/data/madt.test.cpp create mode 100644 libs/acpi/acpi/data/madt.tests.cpp delete mode 100644 libs/acpi/acpi/data/rsdt.test.cpp create mode 100644 libs/acpi/acpi/data/rsdt.tests.cpp delete mode 100644 libs/acpi/acpi/data/xsdt.test.cpp create mode 100644 libs/acpi/acpi/data/xsdt.tests.cpp delete mode 100644 libs/acpi/acpi/pointers.test.cpp create mode 100644 libs/acpi/acpi/pointers.tests.cpp delete mode 100644 libs/kstd/kstd/bits/observer_ptr.test.cpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.tests.cpp delete mode 100644 libs/kstd/kstd/flat_map.test.cpp create mode 100644 libs/kstd/kstd/flat_map.tests.cpp delete mode 100644 libs/kstd/kstd/format.test.cpp create mode 100644 libs/kstd/kstd/format.tests.cpp delete mode 100644 libs/kstd/kstd/string.test.cpp create mode 100644 libs/kstd/kstd/string.tests.cpp delete mode 100644 libs/kstd/kstd/test_support/os_panic.test.cpp create mode 100644 libs/kstd/kstd/test_support/os_panic.tests.cpp delete mode 100644 libs/kstd/kstd/vector.test.cpp create mode 100644 libs/kstd/kstd/vector.tests.cpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 58661c5e..76941dcf 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -92,11 +92,11 @@ if(BUILD_TESTING) add_executable("acpi::tests" ALIAS "acpi_tests") target_sources("acpi_tests" PRIVATE - "acpi/common/table_header.test.cpp" - "acpi/data/madt.test.cpp" - "acpi/data/rsdt.test.cpp" - "acpi/data/xsdt.test.cpp" - "acpi/pointers.test.cpp" + "acpi/common/table_header.tests.cpp" + "acpi/data/madt.tests.cpp" + "acpi/data/rsdt.tests.cpp" + "acpi/data/xsdt.tests.cpp" + "acpi/pointers.tests.cpp" "acpi/test_data/tables.S" ) diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp deleted file mode 100644 index bbd42bd6..00000000 --- a/libs/acpi/acpi/common/table_header.test.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include - -#include - -#include - -SCENARIO("Common table header parsing", "[common_table_header]") -{ - GIVEN("A valid compiled table header") - { - auto data = acpi::test_data::tables::table_header(); - - WHEN("parsing the header") - { - auto header = reinterpret_cast(data.data()); - - THEN("the signature is correct") - { - REQUIRE(header->signature() == "TEST"); - } - - THEN("the revision is correct") - { - REQUIRE(header->revision() == 1); - } - - THEN("the length is correct") - { - REQUIRE(header->length() == kstd::type_size); - } - - THEN("the oem id is correct") - { - REQUIRE(header->oem_id() == "FEMO "); - } - - THEN("the oem table id is correct") - { - REQUIRE(header->oem_table_id() == "HDRTEST "); - } - - THEN("the oem revision is correct") - { - REQUIRE(header->oem_revision() == 1); - } - - THEN("the creator id is correct") - { - REQUIRE(header->creator_id() == "INTL"); - } - - THEN("the creator revision is non-zero") - { - REQUIRE(header->creator_revision() != 0); - } - } - } -} diff --git a/libs/acpi/acpi/common/table_header.tests.cpp b/libs/acpi/acpi/common/table_header.tests.cpp new file mode 100644 index 00000000..bbd42bd6 --- /dev/null +++ b/libs/acpi/acpi/common/table_header.tests.cpp @@ -0,0 +1,60 @@ +#include + +#include + +#include + +#include + +SCENARIO("Common table header parsing", "[common_table_header]") +{ + GIVEN("A valid compiled table header") + { + auto data = acpi::test_data::tables::table_header(); + + WHEN("parsing the header") + { + auto header = reinterpret_cast(data.data()); + + THEN("the signature is correct") + { + REQUIRE(header->signature() == "TEST"); + } + + THEN("the revision is correct") + { + REQUIRE(header->revision() == 1); + } + + THEN("the length is correct") + { + REQUIRE(header->length() == kstd::type_size); + } + + THEN("the oem id is correct") + { + REQUIRE(header->oem_id() == "FEMO "); + } + + THEN("the oem table id is correct") + { + REQUIRE(header->oem_table_id() == "HDRTEST "); + } + + THEN("the oem revision is correct") + { + REQUIRE(header->oem_revision() == 1); + } + + THEN("the creator id is correct") + { + REQUIRE(header->creator_id() == "INTL"); + } + + THEN("the creator revision is non-zero") + { + REQUIRE(header->creator_revision() != 0); + } + } + } +} diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp deleted file mode 100644 index 9ec0180a..00000000 --- a/libs/acpi/acpi/data/madt.test.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include - -#include - -#include - -#include - -#include - -SCENARIO("MADT parsing", "[madt]") -{ - GIVEN("The basic compiled MADT containing a single LAPIC entry and the default x86 LAPIC address") - { - auto data = acpi::test_data::tables::basic_madt(); - - WHEN("parsing the table") - { - auto madt = reinterpret_cast(data.data()); - - THEN("the signature is correct") - { - REQUIRE(madt->signature() == "APIC"); - } - - THEN("validate returns true") - { - REQUIRE(madt->validate()); - } - - THEN("there is a single entry in the table") - { - REQUIRE(std::distance(madt->begin(), madt->end()) == 1); - } - - THEN("the LAPIC address is 0xfee00000") - { - REQUIRE(madt->local_interrupt_controller_address() == 0xfee0'0000); - } - - THEN("the length is sizeof(madt) + sizeof(processor_local_apic)") - { - REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry)); - } - - THEN("the first entry has type processor_local_apic") - { - REQUIRE(madt->cbegin()->type() == acpi::madt_entry::type::processor_local_apic); - } - - THEN("`only` can be used to get a view of all processor_local_apic entries") - { - REQUIRE(std::ranges::distance(madt->only()) == 1); - } - } - } -} diff --git a/libs/acpi/acpi/data/madt.tests.cpp b/libs/acpi/acpi/data/madt.tests.cpp new file mode 100644 index 00000000..9ec0180a --- /dev/null +++ b/libs/acpi/acpi/data/madt.tests.cpp @@ -0,0 +1,57 @@ +#include + +#include + +#include + +#include + +#include + +SCENARIO("MADT parsing", "[madt]") +{ + GIVEN("The basic compiled MADT containing a single LAPIC entry and the default x86 LAPIC address") + { + auto data = acpi::test_data::tables::basic_madt(); + + WHEN("parsing the table") + { + auto madt = reinterpret_cast(data.data()); + + THEN("the signature is correct") + { + REQUIRE(madt->signature() == "APIC"); + } + + THEN("validate returns true") + { + REQUIRE(madt->validate()); + } + + THEN("there is a single entry in the table") + { + REQUIRE(std::distance(madt->begin(), madt->end()) == 1); + } + + THEN("the LAPIC address is 0xfee00000") + { + REQUIRE(madt->local_interrupt_controller_address() == 0xfee0'0000); + } + + THEN("the length is sizeof(madt) + sizeof(processor_local_apic)") + { + REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry)); + } + + THEN("the first entry has type processor_local_apic") + { + REQUIRE(madt->cbegin()->type() == acpi::madt_entry::type::processor_local_apic); + } + + THEN("`only` can be used to get a view of all processor_local_apic entries") + { + REQUIRE(std::ranges::distance(madt->only()) == 1); + } + } + } +} diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp deleted file mode 100644 index 826d0b4f..00000000 --- a/libs/acpi/acpi/data/rsdt.test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include - -SCENARIO("RSDT parsing", "[rsdt]") -{ - GIVEN("The basic compiled RSDT containing 8 table pointers") - { - auto data = acpi::test_data::tables::basic_rsdt(); - - WHEN("parsing the table") - { - auto rsdt = reinterpret_cast(data.data()); - - THEN("the signature is correct") - { - REQUIRE(rsdt->signature() == "RSDT"); - } - - THEN("validate returns true") - { - REQUIRE(rsdt->validate()); - } - - THEN("there are 8 entries in the table") - { - REQUIRE(std::distance(rsdt->begin(), rsdt->end()) == 8); - } - - THEN("the first entry has address 0x10") - { - REQUIRE(rsdt->cbegin()->address() == reinterpret_cast(0x10)); - } - - THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)") - { - REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry)); - } - } - } -} diff --git a/libs/acpi/acpi/data/rsdt.tests.cpp b/libs/acpi/acpi/data/rsdt.tests.cpp new file mode 100644 index 00000000..826d0b4f --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.tests.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include + +#include + +#include + +SCENARIO("RSDT parsing", "[rsdt]") +{ + GIVEN("The basic compiled RSDT containing 8 table pointers") + { + auto data = acpi::test_data::tables::basic_rsdt(); + + WHEN("parsing the table") + { + auto rsdt = reinterpret_cast(data.data()); + + THEN("the signature is correct") + { + REQUIRE(rsdt->signature() == "RSDT"); + } + + THEN("validate returns true") + { + REQUIRE(rsdt->validate()); + } + + THEN("there are 8 entries in the table") + { + REQUIRE(std::distance(rsdt->begin(), rsdt->end()) == 8); + } + + THEN("the first entry has address 0x10") + { + REQUIRE(rsdt->cbegin()->address() == reinterpret_cast(0x10)); + } + + THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)") + { + REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry)); + } + } + } +} diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp deleted file mode 100644 index 19d9bcdb..00000000 --- a/libs/acpi/acpi/data/xsdt.test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include - -SCENARIO("XSDT parsing", "[xsdt]") -{ - GIVEN("The basic compiled XSDT containing 8 table pointers") - { - auto data = acpi::test_data::tables::basic_xsdt(); - - WHEN("parsing the table") - { - auto xsdt = reinterpret_cast(data.data()); - - THEN("the signature is correct") - { - REQUIRE(xsdt->signature() == "XSDT"); - } - - THEN("validate returns true") - { - REQUIRE(xsdt->validate()); - } - - THEN("there are 8 entries in the table") - { - REQUIRE(std::distance(xsdt->begin(), xsdt->end()) == 8); - } - - THEN("the first entry has address 0x10") - { - REQUIRE(xsdt->cbegin()->address() == reinterpret_cast(0x10)); - } - - THEN("the length is sizeof(xsdt) + 8 * sizeof(xsdt_entry)") - { - REQUIRE(xsdt->length().value == sizeof(acpi::xsdt) + 8 * sizeof(acpi::xsdt_entry)); - } - } - } -} diff --git a/libs/acpi/acpi/data/xsdt.tests.cpp b/libs/acpi/acpi/data/xsdt.tests.cpp new file mode 100644 index 00000000..19d9bcdb --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.tests.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include + +#include + +#include + +SCENARIO("XSDT parsing", "[xsdt]") +{ + GIVEN("The basic compiled XSDT containing 8 table pointers") + { + auto data = acpi::test_data::tables::basic_xsdt(); + + WHEN("parsing the table") + { + auto xsdt = reinterpret_cast(data.data()); + + THEN("the signature is correct") + { + REQUIRE(xsdt->signature() == "XSDT"); + } + + THEN("validate returns true") + { + REQUIRE(xsdt->validate()); + } + + THEN("there are 8 entries in the table") + { + REQUIRE(std::distance(xsdt->begin(), xsdt->end()) == 8); + } + + THEN("the first entry has address 0x10") + { + REQUIRE(xsdt->cbegin()->address() == reinterpret_cast(0x10)); + } + + THEN("the length is sizeof(xsdt) + 8 * sizeof(xsdt_entry)") + { + REQUIRE(xsdt->length().value == sizeof(acpi::xsdt) + 8 * sizeof(acpi::xsdt_entry)); + } + } + } +} diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp deleted file mode 100644 index d7b700d6..00000000 --- a/libs/acpi/acpi/pointers.test.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#include - -#include -#include -#include - -SCENARIO("ACPI root pointer parsing", "[acpi]") -{ - GIVEN("A null-filled pointer") - { - auto data = std::array{}; - - WHEN("parsing the data") - { - auto rsdp = std::bit_cast(data); - - THEN("the signature is invalid") - { - REQUIRE(rsdp.signature() != "RSD PTR "); - } - - THEN("validate returns false") - { - REQUIRE_FALSE(rsdp.validate()); - } - } - } -} diff --git a/libs/acpi/acpi/pointers.tests.cpp b/libs/acpi/acpi/pointers.tests.cpp new file mode 100644 index 00000000..d7b700d6 --- /dev/null +++ b/libs/acpi/acpi/pointers.tests.cpp @@ -0,0 +1,30 @@ +#include + +#include + +#include +#include +#include + +SCENARIO("ACPI root pointer parsing", "[acpi]") +{ + GIVEN("A null-filled pointer") + { + auto data = std::array{}; + + WHEN("parsing the data") + { + auto rsdp = std::bit_cast(data); + + THEN("the signature is invalid") + { + REQUIRE(rsdp.signature() != "RSD PTR "); + } + + THEN("validate returns false") + { + REQUIRE_FALSE(rsdp.validate()); + } + } + } +} diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 002fb548..2c63f87d 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -74,12 +74,12 @@ if(BUILD_TESTING) add_executable("kstd::tests" ALIAS "kstd_tests") target_sources("kstd_tests" PRIVATE - "kstd/flat_map.test.cpp" - "kstd/format.test.cpp" - "kstd/vector.test.cpp" - "kstd/bits/observer_ptr.test.cpp" - "kstd/test_support/os_panic.test.cpp" - "kstd/string.test.cpp" + "kstd/flat_map.tests.cpp" + "kstd/format.tests.cpp" + "kstd/vector.tests.cpp" + "kstd/bits/observer_ptr.tests.cpp" + "kstd/test_support/os_panic.tests.cpp" + "kstd/string.tests.cpp" ) target_link_libraries("kstd_tests" PRIVATE diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp deleted file mode 100644 index eca14ccd..00000000 --- a/libs/kstd/kstd/bits/observer_ptr.test.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include -#include - -#include - -#include -#include -#include -#include - -namespace -{ - struct base - { - }; - - struct derived : base - { - }; - - struct element - { - int value{}; - - constexpr auto operator<=>(element const &) const noexcept = default; - }; -} // namespace - -SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto ptr = kstd::observer_ptr{}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a nullptr") - { - auto ptr = kstd::observer_ptr{nullptr}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a raw pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - THEN("the observer pointer is not null") - { - REQUIRE(ptr); - } - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - } - - WHEN("copy constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy constructing from an existing observer pointer with a compatible type") - { - auto value = derived{}; - auto ptr = kstd::observer_ptr(&value); - kstd::observer_ptr copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("constructing an observer pointer using make_observer") - { - auto value = 1; - auto ptr = kstd::make_observer(&value); - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - - THEN("the observe pointer has the correct element type") - { - STATIC_REQUIRE(std::is_same_v>); - } - } - } -} - -SCENARIO("Observer pointer modifiers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("releasing the observer pointer") - { - auto raw_ptr = ptr.release(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - - THEN("the returned pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("resetting the observer pointer to nullptr") - { - ptr.reset(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("resetting the observer pointer to a new object") - { - auto other_value = 2; - ptr.reset(&other_value); - - THEN("the observer pointer points to the new object") - { - REQUIRE(&*ptr == &other_value); - } - } - - WHEN("swapping it with another observer pointer") - { - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - ptr.swap(other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - - WHEN("using namespace-level swap to swap it with another observer pointer") - { - using std::swap; - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - swap(ptr, other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - } -} - -SCENARIO("Observer pointer observers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = element{1}; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("getting the raw pointer") - { - auto raw_ptr = ptr.get(); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("dereferencing the observer pointer") - { - auto dereferenced = *ptr; - - THEN("the dereferenced value is the correct value") - { - REQUIRE(dereferenced == value); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - ptr->value = 2; - - THEN("the value is updated") - { - REQUIRE(value.value == 2); - } - } - - WHEN("converting the observer pointer to a raw pointer") - { - auto raw_ptr = static_cast(ptr); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns true") - { - REQUIRE(static_cast(ptr)); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns false") - { - REQUIRE_FALSE(static_cast(ptr)); - } - } - - WHEN("dereferencing the observer pointer") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Observer pointer comparisons", "[observer_ptr]") -{ - GIVEN("Observer pointers to elements of an array") - { - auto arr = std::array{1, 2}; - auto ptr1 = kstd::observer_ptr{&arr[0]}; - auto ptr2 = kstd::observer_ptr{&arr[1]}; - - WHEN("comparing the same observer pointer") - { - THEN("they are equal") - { - REQUIRE(ptr1 == ptr1); - REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); - } - } - - WHEN("comparing different observer pointers") - { - THEN("they are ordered correctly") - { - REQUIRE(ptr1 != ptr2); - REQUIRE(ptr1 < ptr2); - REQUIRE(ptr1 <= ptr2); - REQUIRE(ptr2 > ptr1); - REQUIRE(ptr2 >= ptr1); - REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); - REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("comparing with another null observer pointer") - { - auto other_ptr = kstd::observer_ptr{}; - - THEN("they are equal") - { - REQUIRE(ptr == other_ptr); - REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.tests.cpp b/libs/kstd/kstd/bits/observer_ptr.tests.cpp new file mode 100644 index 00000000..eca14ccd --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.tests.cpp @@ -0,0 +1,360 @@ +#include +#include + +#include + +#include +#include +#include +#include + +namespace +{ + struct base + { + }; + + struct derived : base + { + }; + + struct element + { + int value{}; + + constexpr auto operator<=>(element const &) const noexcept = default; + }; +} // namespace + +SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto ptr = kstd::observer_ptr{}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a nullptr") + { + auto ptr = kstd::observer_ptr{nullptr}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a raw pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + THEN("the observer pointer is not null") + { + REQUIRE(ptr); + } + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + } + + WHEN("copy constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy constructing from an existing observer pointer with a compatible type") + { + auto value = derived{}; + auto ptr = kstd::observer_ptr(&value); + kstd::observer_ptr copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("constructing an observer pointer using make_observer") + { + auto value = 1; + auto ptr = kstd::make_observer(&value); + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + + THEN("the observe pointer has the correct element type") + { + STATIC_REQUIRE(std::is_same_v>); + } + } + } +} + +SCENARIO("Observer pointer modifiers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("releasing the observer pointer") + { + auto raw_ptr = ptr.release(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + + THEN("the returned pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("resetting the observer pointer to nullptr") + { + ptr.reset(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("resetting the observer pointer to a new object") + { + auto other_value = 2; + ptr.reset(&other_value); + + THEN("the observer pointer points to the new object") + { + REQUIRE(&*ptr == &other_value); + } + } + + WHEN("swapping it with another observer pointer") + { + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + ptr.swap(other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + + WHEN("using namespace-level swap to swap it with another observer pointer") + { + using std::swap; + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + swap(ptr, other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + } +} + +SCENARIO("Observer pointer observers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = element{1}; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("getting the raw pointer") + { + auto raw_ptr = ptr.get(); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("dereferencing the observer pointer") + { + auto dereferenced = *ptr; + + THEN("the dereferenced value is the correct value") + { + REQUIRE(dereferenced == value); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + ptr->value = 2; + + THEN("the value is updated") + { + REQUIRE(value.value == 2); + } + } + + WHEN("converting the observer pointer to a raw pointer") + { + auto raw_ptr = static_cast(ptr); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns true") + { + REQUIRE(static_cast(ptr)); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns false") + { + REQUIRE_FALSE(static_cast(ptr)); + } + } + + WHEN("dereferencing the observer pointer") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Observer pointer comparisons", "[observer_ptr]") +{ + GIVEN("Observer pointers to elements of an array") + { + auto arr = std::array{1, 2}; + auto ptr1 = kstd::observer_ptr{&arr[0]}; + auto ptr2 = kstd::observer_ptr{&arr[1]}; + + WHEN("comparing the same observer pointer") + { + THEN("they are equal") + { + REQUIRE(ptr1 == ptr1); + REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); + } + } + + WHEN("comparing different observer pointers") + { + THEN("they are ordered correctly") + { + REQUIRE(ptr1 != ptr2); + REQUIRE(ptr1 < ptr2); + REQUIRE(ptr1 <= ptr2); + REQUIRE(ptr2 > ptr1); + REQUIRE(ptr2 >= ptr1); + REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); + REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("comparing with another null observer pointer") + { + auto other_ptr = kstd::observer_ptr{}; + + THEN("they are equal") + { + REQUIRE(ptr == other_ptr); + REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp deleted file mode 100644 index bfd02303..00000000 --- a/libs/kstd/kstd/flat_map.test.cpp +++ /dev/null @@ -1,351 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -SCENARIO("Flat Map initialization and construction", "[flat_map]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto map = kstd::flat_map{}; - - THEN("the Flat Map does not contain elements") - { - REQUIRE_FALSE(map.contains(1)); - } - - THEN("the keys container is empty") - { - REQUIRE(map.keys().empty()); - } - - THEN("the values container is empty") - { - REQUIRE(map.values().empty()); - } - } - } -} - -SCENARIO("Flat Map modifiers", "[flat_map]") -{ - GIVEN("An empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("emplacing a new element") - { - auto [it, inserted] = map.emplace(1, 100); - - THEN("the map contains the new element") - { - REQUIRE(inserted); - REQUIRE(map.contains(1)); - } - } - - WHEN("emplacing an existing element") - { - map.emplace(1, 100); - auto [it, inserted] = map.emplace(1, 200); - - THEN("the map does not insert the duplicate") - { - REQUIRE_FALSE(inserted); - REQUIRE(map.contains(1)); - } - } - - AND_GIVEN("a populated Flat Map") - { - auto other = kstd::flat_map{}; - other.emplace(1, 10); - other.emplace(2, 20); - other.emplace(3, 30); - - WHEN("assigning the populated Flat Map to the empty one") - { - map = other; - - THEN("the elements are copied") - { - REQUIRE(map.at(1) == 10); - REQUIRE(map.at(2) == 20); - REQUIRE(map.at(3) == 30); - } - - THEN("the elements are still in the populated Flat Map") - { - REQUIRE(other.at(1) == 10); - REQUIRE(other.at(2) == 20); - REQUIRE(other.at(3) == 30); - } - } - } - } -} - -SCENARIO("Flat Map element access", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with at()") - { - auto & val = map.at(2); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2) == 200); - } - } - - WHEN("accessing a non-existent element with at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map") - { - auto map_builder = kstd::flat_map{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with const at()") - { - auto const & val = map.at(2); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with const at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Flat Map iterators", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = map.begin(); - REQUIRE(it != map.end()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = map.cbegin(); - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.cend()); - } - - THEN("assignment through the proxy modifies the mapped value") - { - auto it = map.begin(); - - *it = std::pair{1, 100}; - - REQUIRE(it->second == 100); - REQUIRE(map.at(1) == 100); - } - - THEN("structured bindings evaluate correctly") - { - auto it = map.cbegin(); - - auto [key, value] = *it; - - REQUIRE(key == 1); - REQUIRE(value == 10); - - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = map.rbegin(); - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = map.crbegin(); - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.crend()); - } - } - } - - GIVEN("an empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(map.begin() == map.end()); - REQUIRE(map.cbegin() == map.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(map.rbegin() == map.rend()); - REQUIRE(map.crbegin() == map.crend()); - } - } - } -} - -SCENARIO("Flat Map heterogeneous element access", "[flat_map]") -{ - GIVEN("A populated Flat Map with a transparent comparator") - { - auto map = kstd::flat_map>{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with a different key type via at()") - { - long const key = 2L; - auto & val = map.at(key); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2L) == 200); - } - } - - WHEN("accessing a non-existent element with a different key type via at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map with a transparent comparator") - { - auto map_builder = kstd::flat_map>{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with a different key type via const at()") - { - long const key = 2L; - auto const & val = map.at(key); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with a different key type via const at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } -} diff --git a/libs/kstd/kstd/flat_map.tests.cpp b/libs/kstd/kstd/flat_map.tests.cpp new file mode 100644 index 00000000..bfd02303 --- /dev/null +++ b/libs/kstd/kstd/flat_map.tests.cpp @@ -0,0 +1,351 @@ +#include + +#include + +#include + +#include +#include +#include + +SCENARIO("Flat Map initialization and construction", "[flat_map]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto map = kstd::flat_map{}; + + THEN("the Flat Map does not contain elements") + { + REQUIRE_FALSE(map.contains(1)); + } + + THEN("the keys container is empty") + { + REQUIRE(map.keys().empty()); + } + + THEN("the values container is empty") + { + REQUIRE(map.values().empty()); + } + } + } +} + +SCENARIO("Flat Map modifiers", "[flat_map]") +{ + GIVEN("An empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("emplacing a new element") + { + auto [it, inserted] = map.emplace(1, 100); + + THEN("the map contains the new element") + { + REQUIRE(inserted); + REQUIRE(map.contains(1)); + } + } + + WHEN("emplacing an existing element") + { + map.emplace(1, 100); + auto [it, inserted] = map.emplace(1, 200); + + THEN("the map does not insert the duplicate") + { + REQUIRE_FALSE(inserted); + REQUIRE(map.contains(1)); + } + } + + AND_GIVEN("a populated Flat Map") + { + auto other = kstd::flat_map{}; + other.emplace(1, 10); + other.emplace(2, 20); + other.emplace(3, 30); + + WHEN("assigning the populated Flat Map to the empty one") + { + map = other; + + THEN("the elements are copied") + { + REQUIRE(map.at(1) == 10); + REQUIRE(map.at(2) == 20); + REQUIRE(map.at(3) == 30); + } + + THEN("the elements are still in the populated Flat Map") + { + REQUIRE(other.at(1) == 10); + REQUIRE(other.at(2) == 20); + REQUIRE(other.at(3) == 30); + } + } + } + } +} + +SCENARIO("Flat Map element access", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with at()") + { + auto & val = map.at(2); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2) == 200); + } + } + + WHEN("accessing a non-existent element with at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map") + { + auto map_builder = kstd::flat_map{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with const at()") + { + auto const & val = map.at(2); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with const at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Flat Map iterators", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = map.begin(); + REQUIRE(it != map.end()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = map.cbegin(); + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.cend()); + } + + THEN("assignment through the proxy modifies the mapped value") + { + auto it = map.begin(); + + *it = std::pair{1, 100}; + + REQUIRE(it->second == 100); + REQUIRE(map.at(1) == 100); + } + + THEN("structured bindings evaluate correctly") + { + auto it = map.cbegin(); + + auto [key, value] = *it; + + REQUIRE(key == 1); + REQUIRE(value == 10); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = map.rbegin(); + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = map.crbegin(); + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.crend()); + } + } + } + + GIVEN("an empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(map.begin() == map.end()); + REQUIRE(map.cbegin() == map.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(map.rbegin() == map.rend()); + REQUIRE(map.crbegin() == map.crend()); + } + } + } +} + +SCENARIO("Flat Map heterogeneous element access", "[flat_map]") +{ + GIVEN("A populated Flat Map with a transparent comparator") + { + auto map = kstd::flat_map>{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with a different key type via at()") + { + long const key = 2L; + auto & val = map.at(key); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2L) == 200); + } + } + + WHEN("accessing a non-existent element with a different key type via at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map with a transparent comparator") + { + auto map_builder = kstd::flat_map>{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with a different key type via const at()") + { + long const key = 2L; + auto const & val = map.at(key); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with a different key type via const at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } +} diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp deleted file mode 100644 index d38a8107..00000000 --- a/libs/kstd/kstd/format.test.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include - -#include - -#include -#include -#include - -using namespace std::string_view_literals; - -SCENARIO("Formatting to a new string", "[format]") -{ - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling format without any arguments.") - { - auto result = kstd::format(fmt); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - auto result = kstd::format(fmt, 1, 2, 3); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - auto result = kstd::format(fmt, 1, true, 'a'); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 1 true a"); - } - } - - WHEN("calling format with too many arguments") - { - auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 2 false b"); - } - } - } -} - -SCENARIO("Formatting to an output iterator", "[format]") -{ - auto buffer = std::ostringstream{}; - - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling format without any arguments.") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); - } - } - - WHEN("calling format with too many arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); - } - } - } -} - -SCENARIO("Determining formatted size") -{ - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling formatted_size without any arguments") - { - auto const size = kstd::formatted_size(fmt); - - THEN("the result is equal to the length of the format string") - { - REQUIRE(size == fmt.size()); - } - } - - WHEN("calling format with additional arguments") - { - auto const size = kstd::formatted_size(fmt, 1, 2, 3); - - THEN("the result is equal to the length of the format string") - { - REQUIRE(size == fmt.size()); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - auto result = kstd::formatted_size(fmt, 1, true, 'a'); - - THEN("the result is the formatted length") - { - REQUIRE(result == std::string_view{"Here are some placeholders: 1 true a"}.size()); - } - } - - WHEN("calling format with too many arguments") - { - auto result = kstd::formatted_size(fmt, 2, false, 'b', 4, 5, 6); - - THEN("the result is the formatted length") - { - REQUIRE(result == std::string_view{"Here are some placeholders: 2 false b"}.size()); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/kstd/format.tests.cpp b/libs/kstd/kstd/format.tests.cpp new file mode 100644 index 00000000..d38a8107 --- /dev/null +++ b/libs/kstd/kstd/format.tests.cpp @@ -0,0 +1,170 @@ +#include + +#include + +#include +#include +#include + +using namespace std::string_view_literals; + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling format without any arguments.") + { + auto result = kstd::format(fmt); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto result = kstd::format(fmt, 1, 2, 3); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, 'a'); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true a"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false b"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling format without any arguments.") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); + } + } + } +} + +SCENARIO("Determining formatted size") +{ + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling formatted_size without any arguments") + { + auto const size = kstd::formatted_size(fmt); + + THEN("the result is equal to the length of the format string") + { + REQUIRE(size == fmt.size()); + } + } + + WHEN("calling format with additional arguments") + { + auto const size = kstd::formatted_size(fmt, 1, 2, 3); + + THEN("the result is equal to the length of the format string") + { + REQUIRE(size == fmt.size()); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::formatted_size(fmt, 1, true, 'a'); + + THEN("the result is the formatted length") + { + REQUIRE(result == std::string_view{"Here are some placeholders: 1 true a"}.size()); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::formatted_size(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted length") + { + REQUIRE(result == std::string_view{"Here are some placeholders: 2 false b"}.size()); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp deleted file mode 100644 index b8fa1139..00000000 --- a/libs/kstd/kstd/string.test.cpp +++ /dev/null @@ -1,3166 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -TEST_CASE("String static interface") -{ - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v>::size_type>); - REQUIRE(std::is_same_v>::difference_type>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>::pointer>); - REQUIRE(std::is_same_v>::const_pointer>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v>); - - REQUIRE(kstd::string::npos == kstd::string::size_type(-1)); -} - -SCENARIO("String initialization and construction", "[string]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto s = kstd::string{}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to zero") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to zero") - { - REQUIRE(s.length() == 0); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing with 10 copies of the letter 'a'") - { - auto s = kstd::string{10, 'a'}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns a") - { - REQUIRE(s.at(s.size() - 1) == 'a'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns a") - { - REQUIRE(s[s.size() - 1] == 'a'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns a") - { - REQUIRE(s.back() == 'a'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with 20 copies of the letter 'a'") - { - auto s = kstd::string{20, 'a'}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns a") - { - REQUIRE(s.at(s.size() - 1) == 'a'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns a") - { - REQUIRE(s[s.size() - 1] == 'a'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns a") - { - REQUIRE(s.back() == 'a'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing with an input iterator pair of 10 characters") - { - auto source = std::istringstream{"abcdefghij"}; - auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns j") - { - REQUIRE(s.at(s.size() - 1) == 'j'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns j") - { - REQUIRE(s[s.size() - 1] == 'j'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with an input iterator pair of 20 characters") - { - auto source = std::istringstream{"abcdefghijABCDEFGHIJ"}; - auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is greater than or equal to 20") - { - REQUIRE(s.capacity() >= 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing with a forward iterator pair of 10 characters") - { - auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - auto s = kstd::string{source.cbegin(), source.cend()}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns j") - { - REQUIRE(s.at(s.size() - 1) == 'j'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns j") - { - REQUIRE(s[s.size() - 1] == 'j'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with a forward iterator pair of 20 characters") - { - auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; - auto s = kstd::string{source.cbegin(), source.cend()}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a null pointer with zero size") - { - auto s = kstd::string{static_cast(nullptr), 0}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to 0") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to 0") - { - REQUIRE(s.length() == 0); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing from a from a null pointer and non-zero size") - { - THEN("a panic is raised") - { - REQUIRE_THROWS_AS((kstd::string{static_cast(nullptr), 1}), kstd::tests::os_panic); - } - } - - WHEN("constructing from a non-null pointer and zero size") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr, 0}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to 0") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to 0") - { - REQUIRE(s.length() == 0); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing from a non-null pointer and a size of 4") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr, 4}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a non-null pointer and a size of 20") - { - auto ptr = "abcdefghijABCDEFGHIJ"; - auto s = kstd::string{ptr, 20}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a C-style string of length 4") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a C-style string of length 20") - { - auto ptr = "abcdefghijABCDEFGHIJ"; - auto s = kstd::string{ptr}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a string view of length 4") - { - using namespace std::string_view_literals; - - auto view = "abcd"sv; - auto s = kstd::string{view}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a string view of length 20") - { - using namespace std::string_view_literals; - - auto view = "abcdefghijABCDEFGHIJ"sv; - auto s = kstd::string{view}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a substring of a string view of length 4") - { - using namespace std::string_view_literals; - - auto view = "abcd"sv; - auto s = kstd::string{view, 1, 4}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 3") - { - REQUIRE(s.size() == 3); - } - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns b") - { - REQUIRE(s.at(0) == 'b'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns b") - { - REQUIRE(s[0] == 'b'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns b") - { - REQUIRE(s.front() == 'b'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an b") - { - REQUIRE(*s.data() == 'b'); - } - - THEN("c_str returns a pointer pointing to an b") - { - REQUIRE(*s.c_str() == 'b'); - } - - THEN("c_str points to the start of a c-string of length 3") - { - REQUIRE(std::strlen(s.c_str()) == 3); - } - - THEN("the string converts to a string view of length 3") - { - auto view = static_cast(s); - REQUIRE(view.length() == 3); - } - } - - WHEN("constructing from a substring of a string view of length 20") - { - using namespace std::string_view_literals; - - auto view = "abcdefghijABCDEFGHIJ"sv; - auto s = kstd::string{view, 1, 20}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 19") - { - REQUIRE(s.size() == 19); - } - - THEN("the length is equal to 19") - { - REQUIRE(s.length() == 19); - } - - THEN("the capacity is equal to 19") - { - REQUIRE(s.capacity() == 19); - } - - THEN("at(0) returns b") - { - REQUIRE(s.at(0) == 'b'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns b") - { - REQUIRE(s[0] == 'b'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns b") - { - REQUIRE(s.front() == 'b'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an b") - { - REQUIRE(*s.data() == 'b'); - } - - THEN("c_str returns a pointer pointing to an b") - { - REQUIRE(*s.c_str() == 'b'); - } - - THEN("c_str points to the start of a c-string of length 19") - { - REQUIRE(std::strlen(s.c_str()) == 19); - } - - THEN("the string converts to a string view of length 19") - { - auto view = static_cast(s); - REQUIRE(view.length() == 19); - } - } - } - - GIVEN("An existing short string, of length 10") - { - auto other = kstd::string{"abcdefghij"}; - - WHEN("constructing by copy") - { - auto s = other; - - THEN("the sizes are identical") - { - REQUIRE(s.size() == other.size()); - } - - THEN("the capacities are idendtical") - { - REQUIRE(s.capacity() == other.capacity()); - } - - THEN("the underlying strings are identical") - { - REQUIRE_FALSE(std::strcmp(s.data(), other.data())); - } - } - - WHEN("constructing by move") - { - auto s = std::move(other); - - THEN("size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("front() return an a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back() return a j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("The moved from object's size is 0") - { - REQUIRE(other.size() == 0); - } - - THEN("The moved from object's capacity is 15") - { - REQUIRE(other.capacity() == 15); - } - - THEN("c_str() on the moved from object points to a null byte") - { - REQUIRE(*other.c_str() == '\0'); - } - } - } - - GIVEN("An existing short string, of length 20") - { - auto other = kstd::string{"abcdefghijABCDEFGHIJ"}; - - WHEN("constructing by copy") - { - auto s = other; - - THEN("the sizes are identical") - { - REQUIRE(s.size() == other.size()); - } - - THEN("the capacities are idendtical") - { - REQUIRE(s.capacity() == other.capacity()); - } - - THEN("the underlying strings are identical") - { - REQUIRE_FALSE(std::strcmp(s.data(), other.data())); - } - } - - WHEN("constructing by move") - { - auto s = std::move(other); - - THEN("size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("front() return an a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back() return a J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("The moved from object's size is 0") - { - REQUIRE(other.size() == 0); - } - - THEN("The moved from object's capacity is 15") - { - REQUIRE(other.capacity() == 15); - } - - THEN("c_str() on the moved from object points to a null byte") - { - REQUIRE(*other.c_str() == '\0'); - } - } - } -} - -SCENARIO("String assignment", "[string]") -{ - GIVEN("Two short strings") - { - auto s1 = kstd::string{"abcd"}; - auto s2 = kstd::string{"defghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 4); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 15); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 7); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 15); - } - } - } - - GIVEN("One short and one long string") - { - auto s1 = kstd::string{"abcd"}; - auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the new capacity of the longer one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the new capacity of the shorter one is 20") - { - REQUIRE(s1.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 4); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 15); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 20); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 20); - } - } - } - - GIVEN("Two long strings") - { - auto s1 = kstd::string{"abcdefghijABCDEFGH"}; - auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the new capacity of the longer one is still 20") - { - REQUIRE(s2.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the new capacity of the shorter one is 20") - { - REQUIRE(s1.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 18); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 18); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 20); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 20); - } - } - } - - GIVEN("An short string") - { - auto s = kstd::string{"abcd"}; - - WHEN("assigning a new, shorter c-style string of length 3") - { - s = "def"; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning a new, longer, c-style string of length 5") - { - s = "defgh"; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning a new, longer, c-style string of length 20") - { - s = "ABCDEFGHIJabcdefghij"; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - - WHEN("assigning a single character") - { - s = 'z'; - - THEN("the length is equal to 1") - { - REQUIRE(s.length() == 1); - } - } - - WHEN("assigning an shorter initializer list of length 3") - { - s = {'d', 'e', 'f'}; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning an longer initializer list of length 5") - { - s = {'d', 'e', 'f', 'g', 'h'}; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning an longer initializer list of length 20") - { - s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - - WHEN("assigning from a shorter string view of length 3") - { - using namespace std::string_view_literals; - - s = "def"sv; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning from a longer string view of length 5") - { - using namespace std::string_view_literals; - - s = "defgh"sv; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning from a longer string view of length 20") - { - using namespace std::string_view_literals; - - s = "ABCDEFGHIJabcdefghij"sv; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - } - - GIVEN("An long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - - WHEN("assigning a new, shorter c-style string of length 3") - { - s = "def"; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning a new, longer, c-style string of length 22") - { - s = "ABCDEFGHIJabcdefghijkl"; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - - WHEN("assigning a single character") - { - s = 'z'; - - THEN("the length is equal to 1") - { - REQUIRE(s.length() == 1); - } - } - - WHEN("assigning an shorter initializer list of length 3") - { - s = {'d', 'e', 'f'}; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning an longer initializer list of length 22") - { - s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', - 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - - WHEN("assigning from a shorter string view of length 3") - { - using namespace std::string_view_literals; - - s = "def"sv; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning from a longer string view of length 22") - { - using namespace std::string_view_literals; - - s = "ABCDEFGHIJabcdefghijkl"sv; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - } -} - -SCENARIO("String modifiers", "[string]") -{ - GIVEN("A short string") - { - auto s = kstd::string{"abcd"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - auto old_data = s.data(); - - WHEN("reserving a smaller capacity") - { - s.reserve(1); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(s.data() == old_data); - } - } - - WHEN("reserving equal capacity") - { - s.reserve(old_capacity); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(static_cast(s.data()) == static_cast(old_data)); - } - } - - WHEN("reserving larger capacity") - { - s.reserve(old_capacity + 1); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() >= old_capacity + 1); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer changes") - { - REQUIRE(s.data() != old_data); - } - } - - WHEN("resizing to a greater size within the capacity") - { - s.resize(10); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 10); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(10, 'x'); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 10); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size outside the capacity") - { - s.resize(20); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 20); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", s.size() + 1) == - 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(20, 'x'); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 20); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxxxxxxxxxxxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a lesser size") - { - s.resize(2); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size decreases") - { - CHECK(s.size() < old_size); - REQUIRE(s.size() == 2); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); - } - } - } - - GIVEN("A long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - auto old_data = s.data(); - - WHEN("reserving a smaller capacity") - { - s.reserve(1); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(s.data() == old_data); - } - } - - WHEN("reserving equal capacity") - { - s.reserve(old_capacity); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(static_cast(s.data()) == static_cast(old_data)); - } - } - - WHEN("reserving larger capacity") - { - s.reserve(old_capacity + 1); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() >= old_capacity + 1); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer changes") - { - REQUIRE(s.data() != old_data); - } - } - - WHEN("resizing to a greater size") - { - s.resize(22); // NOLINT - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 22); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJ\0\0", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(22, 'x'); // NOLINT - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 22); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a lesser size") - { - s.resize(2); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size decreases") - { - CHECK(s.size() < old_size); - REQUIRE(s.size() == 2); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); - } - } - } - - GIVEN("Two strings") - { - auto str1 = kstd::string{"Blub"}; - auto str2 = kstd::string{" Blub"}; - - WHEN("appending the second string to the first string") - { - str1.append(str2); - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1 == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - - WHEN("using operator+= to append the second string to the first string") - { - str1 += str2; - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1 == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - } - - GIVEN("A string and a string view") - { - auto str = kstd::string{"Blub"}; - auto view = std::string_view{" Blub"}; - - WHEN("appending the string view to the string") - { - str.append(view); - - THEN("the string contains the characters of both the original string and the appended view concatenated") - { - REQUIRE(str == "Blub Blub"); - } - - THEN("the size of the string is the sum of the sizes of the original string and the appended view") - { - REQUIRE(str.size() == view.size() + 4); - } - } - } - - GIVEN("A string and a character") - { - auto str = kstd::string{"Blub"}; - auto ch = '!'; - - WHEN("appending the character to the string") - { - str.push_back(ch); - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - - WHEN("using operator+= to append the character to the string") - { - str += ch; - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - } - - GIVEN("A non-empty string") - { - auto str = kstd::string{"Blub Blub"}; - - WHEN("clearing the string") - { - str.clear(); - - THEN("the string is empty and has size zero") - { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - - THEN("the string contains no characters") - { - REQUIRE(str == std::string_view{}); - } - } - } -} - -SCENARIO("String insertion", "[string]") -{ - GIVEN("A short string") - { - auto s = kstd::string{"abcd"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - - WHEN("inserting five copies of a character at the beginning") - { - s.insert(0, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcd"}); - } - } - - WHEN("inserting five copies of a character in the middle") - { - s.insert(2, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAcd"}); - } - } - - WHEN("inserting five copies of a character at the end") - { - s.insert(4, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdAAAAA"}); - } - } - - WHEN("inserting five copies of a character behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(5, 5, 'A'), kstd::tests::os_panic); - } - } - - WHEN("inserting 20 copies of a character") - { - s.insert(2, 20, 'A'); - - THEN("the capacity of the string grows") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the string is equal t o the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAAAAAAAAAAAAAAAAcd"}); - } - } - - WHEN("inserting a C-style string of length 5 at the beginning") - { - s.insert(0, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcd"}); - } - } - - WHEN("inserting a C-style string of length 5 in the middle") - { - s.insert(2, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAcd"}); - } - } - - WHEN("inserting a C-style string of length 5 at the end") - { - s.insert(4, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdAAAAA"}); - } - } - - WHEN("inserting a C-style string of length 5 behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(5, "AAAAA"), kstd::tests::os_panic); - } - } - } - - GIVEN("A long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - - WHEN("inserting five copies of a character at the beginning") - { - s.insert(0, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); - } - } - - WHEN("inserting five copies of a character in the middle") - { - s.insert(10, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); - } - } - - WHEN("inserting five copies of a character at the end") - { - s.insert(20, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); - } - } - - WHEN("inserting five copies of a character behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(21, 5, 'A'), kstd::tests::os_panic); - } - } - - WHEN("inserting a C-style string of length 5 at the beginning") - { - s.insert(0, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); - } - } - - WHEN("inserting a C-style string of length 5 in the middle") - { - s.insert(10, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); - } - } - - WHEN("inserting a C-style string of length 5 at the end") - { - s.insert(20, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); - } - } - - WHEN("inserting a C-style string of length 5 behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(21, "AAAAA"), kstd::tests::os_panic); - } - } - } -} - -SCENARIO("String concatenation", "[string]") -{ - GIVEN("Two strings") - { - auto str1 = kstd::string{"Blub"}; - auto str2 = kstd::string{" Blub"}; - - WHEN("using operator+ to concatenate the two strings into a new string") - { - auto str3 = str1 + str2; - - THEN("the new string contains the characters of both strings concatenated") - { - REQUIRE(str3 == "Blub Blub"); - } - - THEN("the size of the new string is the sum of the sizes of both strings") - { - REQUIRE(str3.size() == str1.size() + str2.size()); - } - } - } -} - -SCENARIO("String conversion and comparison", "[string]") -{ - GIVEN("An unsigned integer") - { - constexpr auto value1 = 12345u; - constexpr auto value2 = 0u; - - WHEN("converting the unsigned integer to a string") - { - auto str1 = kstd::to_string(value1); - auto str2 = kstd::to_string(value2); - - THEN("the string contains the decimal representation of the unsigned integer") - { - REQUIRE(str1 == "12345"); - REQUIRE(str2 == "0"); - } - } - } - - GIVEN("Two strings with the same characters") - { - auto str1 = kstd::string{"Blub Blub"}; - auto str2 = kstd::string{"Blub Blub"}; - - THEN("the strings are equal") - { - REQUIRE(str1 == str2); - } - - THEN("the strings are not unequal") - { - REQUIRE_FALSE(str1 != str2); - } - } - - GIVEN("A string and a string view with the same characters") - { - auto str = kstd::string{"Blub Blub"}; - auto view = std::string_view{"Blub Blub"}; - - THEN("the string and the string view are equal") - { - REQUIRE(str == view); - REQUIRE(view == str); - } - - THEN("the string and the string view are not unequal") - { - REQUIRE_FALSE(str != view); - REQUIRE_FALSE(view != str); - } - } - - GIVEN("The string 'abc'") - { - auto s1 = kstd::string{"abc"}; - - WHEN("comparing with the string 'def'") - { - auto result = s1.compare(kstd::string{"def"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing with the string 'abb'") - { - auto result = s1.compare(kstd::string{"abb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing with the string 'abc'") - { - auto result = s1.compare(kstd::string{"abc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the string 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the string 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the string 'cc'") - { - auto result = s1.compare(1, 2, kstd::string{"bc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'ef'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"ef"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'bb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"bb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'bc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"bc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}, 1, 2); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, 2); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'abc'") - { - auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, 2); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'def'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, 2); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'abb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, 2); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'abc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, 2); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}, 1, kstd::string::npos); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, kstd::string::npos); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'abc'") - { - auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, kstd::string::npos); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'def'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, kstd::string::npos); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, kstd::string::npos); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, kstd::string::npos); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing with the C-style string 'def'") - { - auto result = s1.compare("def"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing with the C-style string 'abb'") - { - auto result = s1.compare("abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing with the C-style string 'abc'") - { - auto result = s1.compare("abc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'def'") - { - auto result = s1.compare(1, 2, "def"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'abb'") - { - auto result = s1.compare(1, 2, "abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'bc'") - { - auto result = s1.compare(1, 2, "bc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'def'") - { - auto result = s1.compare(1, 2, "ef"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'abb'") - { - auto result = s1.compare(1, 2, "abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'bc'") - { - auto result = s1.compare(1, 2, "bc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - } -} - -SCENARIO("String iteration", "[string]") -{ - GIVEN("A string") - { - auto str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using std::ranges::for_each to iterate over the characters of the string") - { - kstd::string result; - - std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("A const string") - { - auto const str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == static_cast(str)); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("An empty string") - { - auto str = kstd::string{}; - - WHEN("iterating over the characters of an empty string") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(static_cast(result) == std::string_view{}); - } - } - } -} - -SCENARIO("String STL integration", "[string]") -{ - GIVEN("An empty string") - { - auto s = kstd::string{""}; - - WHEN("hashing the string") - { - auto hash_result = std::hash{}(s); - - THEN("the result is equal to hashing an empty std::string") - { - REQUIRE(hash_result == std::hash{}("")); - } - } - } - - GIVEN("A non-empty string") - { - auto s = kstd::string{"abcd"}; - - WHEN("hashing the string") - { - auto hash_result = std::hash{}(s); - - THEN("the result is equal to hashing the same std::string") - { - REQUIRE(hash_result == std::hash{}("abcd")); - } - } - } -} - -SCENARIO("String user-defined literals") -{ - GIVEN("the user defined literals are in scope") - { - using namespace kstd::string_literals; - - WHEN("creating a string using the _s literal suffix") - { - auto s = "abcd"_s; - - THEN("the result is equal to the string created with the same literal") - { - REQUIRE(s == kstd::string{"abcd"}); - } - } - } -} diff --git a/libs/kstd/kstd/string.tests.cpp b/libs/kstd/kstd/string.tests.cpp new file mode 100644 index 00000000..b8fa1139 --- /dev/null +++ b/libs/kstd/kstd/string.tests.cpp @@ -0,0 +1,3166 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE("String static interface") +{ + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>::size_type>); + REQUIRE(std::is_same_v>::difference_type>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>::pointer>); + REQUIRE(std::is_same_v>::const_pointer>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>); + + REQUIRE(kstd::string::npos == kstd::string::size_type(-1)); +} + +SCENARIO("String initialization and construction", "[string]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto s = kstd::string{}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to zero") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to zero") + { + REQUIRE(s.length() == 0); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing with 10 copies of the letter 'a'") + { + auto s = kstd::string{10, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with 20 copies of the letter 'a'") + { + auto s = kstd::string{20, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with an input iterator pair of 10 characters") + { + auto source = std::istringstream{"abcdefghij"}; + auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with an input iterator pair of 20 characters") + { + auto source = std::istringstream{"abcdefghijABCDEFGHIJ"}; + auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is greater than or equal to 20") + { + REQUIRE(s.capacity() >= 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with a forward iterator pair of 10 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + auto s = kstd::string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with a forward iterator pair of 20 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; + auto s = kstd::string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a null pointer with zero size") + { + auto s = kstd::string{static_cast(nullptr), 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a from a null pointer and non-zero size") + { + THEN("a panic is raised") + { + REQUIRE_THROWS_AS((kstd::string{static_cast(nullptr), 1}), kstd::tests::os_panic); + } + } + + WHEN("constructing from a non-null pointer and zero size") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr, 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a non-null pointer and a size of 4") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a non-null pointer and a size of 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = kstd::string{ptr, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a C-style string of length 4") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a C-style string of length 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = kstd::string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = kstd::string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = kstd::string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a substring of a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = kstd::string{view, 1, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 3") + { + REQUIRE(s.size() == 3); + } + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 3") + { + REQUIRE(std::strlen(s.c_str()) == 3); + } + + THEN("the string converts to a string view of length 3") + { + auto view = static_cast(s); + REQUIRE(view.length() == 3); + } + } + + WHEN("constructing from a substring of a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = kstd::string{view, 1, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 19") + { + REQUIRE(s.size() == 19); + } + + THEN("the length is equal to 19") + { + REQUIRE(s.length() == 19); + } + + THEN("the capacity is equal to 19") + { + REQUIRE(s.capacity() == 19); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 19") + { + REQUIRE(std::strlen(s.c_str()) == 19); + } + + THEN("the string converts to a string view of length 19") + { + auto view = static_cast(s); + REQUIRE(view.length() == 19); + } + } + } + + GIVEN("An existing short string, of length 10") + { + auto other = kstd::string{"abcdefghij"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } + + GIVEN("An existing short string, of length 20") + { + auto other = kstd::string{"abcdefghijABCDEFGHIJ"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } +} + +SCENARIO("String assignment", "[string]") +{ + GIVEN("Two short strings") + { + auto s1 = kstd::string{"abcd"}; + auto s2 = kstd::string{"defghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 7); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 15); + } + } + } + + GIVEN("One short and one long string") + { + auto s1 = kstd::string{"abcd"}; + auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("Two long strings") + { + auto s1 = kstd::string{"abcdefghijABCDEFGH"}; + auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is still 20") + { + REQUIRE(s2.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 18); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 18); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("An short string") + { + auto s = kstd::string{"abcd"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 5") + { + s = "defgh"; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning a new, longer, c-style string of length 20") + { + s = "ABCDEFGHIJabcdefghij"; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 5") + { + s = {'d', 'e', 'f', 'g', 'h'}; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning an longer initializer list of length 20") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 5") + { + using namespace std::string_view_literals; + + s = "defgh"sv; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning from a longer string view of length 20") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghij"sv; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + } + + GIVEN("An long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 22") + { + s = "ABCDEFGHIJabcdefghijkl"; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 22") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', + 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 22") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghijkl"sv; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + } +} + +SCENARIO("String modifiers", "[string]") +{ + GIVEN("A short string") + { + auto s = kstd::string{"abcd"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + auto old_data = s.data(); + + WHEN("reserving a smaller capacity") + { + s.reserve(1); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(s.data() == old_data); + } + } + + WHEN("reserving equal capacity") + { + s.reserve(old_capacity); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(static_cast(s.data()) == static_cast(old_data)); + } + } + + WHEN("reserving larger capacity") + { + s.reserve(old_capacity + 1); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() >= old_capacity + 1); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer changes") + { + REQUIRE(s.data() != old_data); + } + } + + WHEN("resizing to a greater size within the capacity") + { + s.resize(10); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 10); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(10, 'x'); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 10); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size outside the capacity") + { + s.resize(20); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 20); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", s.size() + 1) == + 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(20, 'x'); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 20); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxxxxxxxxxxxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a lesser size") + { + s.resize(2); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size decreases") + { + CHECK(s.size() < old_size); + REQUIRE(s.size() == 2); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); + } + } + } + + GIVEN("A long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + auto old_data = s.data(); + + WHEN("reserving a smaller capacity") + { + s.reserve(1); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(s.data() == old_data); + } + } + + WHEN("reserving equal capacity") + { + s.reserve(old_capacity); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(static_cast(s.data()) == static_cast(old_data)); + } + } + + WHEN("reserving larger capacity") + { + s.reserve(old_capacity + 1); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() >= old_capacity + 1); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer changes") + { + REQUIRE(s.data() != old_data); + } + } + + WHEN("resizing to a greater size") + { + s.resize(22); // NOLINT + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 22); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJ\0\0", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(22, 'x'); // NOLINT + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 22); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a lesser size") + { + s.resize(2); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size decreases") + { + CHECK(s.size() < old_size); + REQUIRE(s.size() == 2); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); + } + } + } + + GIVEN("Two strings") + { + auto str1 = kstd::string{"Blub"}; + auto str2 = kstd::string{" Blub"}; + + WHEN("appending the second string to the first string") + { + str1.append(str2); + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1 == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + + WHEN("using operator+= to append the second string to the first string") + { + str1 += str2; + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1 == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + } + + GIVEN("A string and a string view") + { + auto str = kstd::string{"Blub"}; + auto view = std::string_view{" Blub"}; + + WHEN("appending the string view to the string") + { + str.append(view); + + THEN("the string contains the characters of both the original string and the appended view concatenated") + { + REQUIRE(str == "Blub Blub"); + } + + THEN("the size of the string is the sum of the sizes of the original string and the appended view") + { + REQUIRE(str.size() == view.size() + 4); + } + } + } + + GIVEN("A string and a character") + { + auto str = kstd::string{"Blub"}; + auto ch = '!'; + + WHEN("appending the character to the string") + { + str.push_back(ch); + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + + WHEN("using operator+= to append the character to the string") + { + str += ch; + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + } + + GIVEN("A non-empty string") + { + auto str = kstd::string{"Blub Blub"}; + + WHEN("clearing the string") + { + str.clear(); + + THEN("the string is empty and has size zero") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string contains no characters") + { + REQUIRE(str == std::string_view{}); + } + } + } +} + +SCENARIO("String insertion", "[string]") +{ + GIVEN("A short string") + { + auto s = kstd::string{"abcd"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + + WHEN("inserting five copies of a character at the beginning") + { + s.insert(0, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcd"}); + } + } + + WHEN("inserting five copies of a character in the middle") + { + s.insert(2, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAcd"}); + } + } + + WHEN("inserting five copies of a character at the end") + { + s.insert(4, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdAAAAA"}); + } + } + + WHEN("inserting five copies of a character behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(5, 5, 'A'), kstd::tests::os_panic); + } + } + + WHEN("inserting 20 copies of a character") + { + s.insert(2, 20, 'A'); + + THEN("the capacity of the string grows") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the string is equal t o the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAAAAAAAAAAAAAAAAcd"}); + } + } + + WHEN("inserting a C-style string of length 5 at the beginning") + { + s.insert(0, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcd"}); + } + } + + WHEN("inserting a C-style string of length 5 in the middle") + { + s.insert(2, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAcd"}); + } + } + + WHEN("inserting a C-style string of length 5 at the end") + { + s.insert(4, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdAAAAA"}); + } + } + + WHEN("inserting a C-style string of length 5 behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(5, "AAAAA"), kstd::tests::os_panic); + } + } + } + + GIVEN("A long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + + WHEN("inserting five copies of a character at the beginning") + { + s.insert(0, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); + } + } + + WHEN("inserting five copies of a character in the middle") + { + s.insert(10, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); + } + } + + WHEN("inserting five copies of a character at the end") + { + s.insert(20, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); + } + } + + WHEN("inserting five copies of a character behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(21, 5, 'A'), kstd::tests::os_panic); + } + } + + WHEN("inserting a C-style string of length 5 at the beginning") + { + s.insert(0, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); + } + } + + WHEN("inserting a C-style string of length 5 in the middle") + { + s.insert(10, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); + } + } + + WHEN("inserting a C-style string of length 5 at the end") + { + s.insert(20, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); + } + } + + WHEN("inserting a C-style string of length 5 behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(21, "AAAAA"), kstd::tests::os_panic); + } + } + } +} + +SCENARIO("String concatenation", "[string]") +{ + GIVEN("Two strings") + { + auto str1 = kstd::string{"Blub"}; + auto str2 = kstd::string{" Blub"}; + + WHEN("using operator+ to concatenate the two strings into a new string") + { + auto str3 = str1 + str2; + + THEN("the new string contains the characters of both strings concatenated") + { + REQUIRE(str3 == "Blub Blub"); + } + + THEN("the size of the new string is the sum of the sizes of both strings") + { + REQUIRE(str3.size() == str1.size() + str2.size()); + } + } + } +} + +SCENARIO("String conversion and comparison", "[string]") +{ + GIVEN("An unsigned integer") + { + constexpr auto value1 = 12345u; + constexpr auto value2 = 0u; + + WHEN("converting the unsigned integer to a string") + { + auto str1 = kstd::to_string(value1); + auto str2 = kstd::to_string(value2); + + THEN("the string contains the decimal representation of the unsigned integer") + { + REQUIRE(str1 == "12345"); + REQUIRE(str2 == "0"); + } + } + } + + GIVEN("Two strings with the same characters") + { + auto str1 = kstd::string{"Blub Blub"}; + auto str2 = kstd::string{"Blub Blub"}; + + THEN("the strings are equal") + { + REQUIRE(str1 == str2); + } + + THEN("the strings are not unequal") + { + REQUIRE_FALSE(str1 != str2); + } + } + + GIVEN("A string and a string view with the same characters") + { + auto str = kstd::string{"Blub Blub"}; + auto view = std::string_view{"Blub Blub"}; + + THEN("the string and the string view are equal") + { + REQUIRE(str == view); + REQUIRE(view == str); + } + + THEN("the string and the string view are not unequal") + { + REQUIRE_FALSE(str != view); + REQUIRE_FALSE(view != str); + } + } + + GIVEN("The string 'abc'") + { + auto s1 = kstd::string{"abc"}; + + WHEN("comparing with the string 'def'") + { + auto result = s1.compare(kstd::string{"def"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing with the string 'abb'") + { + auto result = s1.compare(kstd::string{"abb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing with the string 'abc'") + { + auto result = s1.compare(kstd::string{"abc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the string 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the string 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the string 'cc'") + { + auto result = s1.compare(1, 2, kstd::string{"bc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'ef'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"ef"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'bb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"bb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'bc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"bc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}, 1, 2); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, 2); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'abc'") + { + auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, 2); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'def'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, 2); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'abb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, 2); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'abc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, 2); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}, 1, kstd::string::npos); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, kstd::string::npos); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'abc'") + { + auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, kstd::string::npos); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'def'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, kstd::string::npos); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, kstd::string::npos); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, kstd::string::npos); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing with the C-style string 'def'") + { + auto result = s1.compare("def"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing with the C-style string 'abb'") + { + auto result = s1.compare("abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing with the C-style string 'abc'") + { + auto result = s1.compare("abc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'def'") + { + auto result = s1.compare(1, 2, "def"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'abb'") + { + auto result = s1.compare(1, 2, "abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'bc'") + { + auto result = s1.compare(1, 2, "bc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'def'") + { + auto result = s1.compare(1, 2, "ef"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'abb'") + { + auto result = s1.compare(1, 2, "abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'bc'") + { + auto result = s1.compare(1, 2, "bc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + } +} + +SCENARIO("String iteration", "[string]") +{ + GIVEN("A string") + { + auto str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using std::ranges::for_each to iterate over the characters of the string") + { + kstd::string result; + + std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("A const string") + { + auto const str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == static_cast(str)); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("An empty string") + { + auto str = kstd::string{}; + + WHEN("iterating over the characters of an empty string") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(static_cast(result) == std::string_view{}); + } + } + } +} + +SCENARIO("String STL integration", "[string]") +{ + GIVEN("An empty string") + { + auto s = kstd::string{""}; + + WHEN("hashing the string") + { + auto hash_result = std::hash{}(s); + + THEN("the result is equal to hashing an empty std::string") + { + REQUIRE(hash_result == std::hash{}("")); + } + } + } + + GIVEN("A non-empty string") + { + auto s = kstd::string{"abcd"}; + + WHEN("hashing the string") + { + auto hash_result = std::hash{}(s); + + THEN("the result is equal to hashing the same std::string") + { + REQUIRE(hash_result == std::hash{}("abcd")); + } + } + } +} + +SCENARIO("String user-defined literals") +{ + GIVEN("the user defined literals are in scope") + { + using namespace kstd::string_literals; + + WHEN("creating a string using the _s literal suffix") + { + auto s = "abcd"_s; + + THEN("the result is equal to the string created with the same literal") + { + REQUIRE(s == kstd::string{"abcd"}); + } + } + } +} diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp deleted file mode 100644 index c30411aa..00000000 --- a/libs/kstd/kstd/test_support/os_panic.test.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#include -#include -#include - -namespace kstd::os -{ - - auto panic(std::string_view message, std::source_location location) - { - throw kstd::tests::os_panic{std::string{message}, location}; - } - -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/test_support/os_panic.tests.cpp b/libs/kstd/kstd/test_support/os_panic.tests.cpp new file mode 100644 index 00000000..c30411aa --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.tests.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include +#include + +namespace kstd::os +{ + + auto panic(std::string_view message, std::source_location location) + { + throw kstd::tests::os_panic{std::string{message}, location}; + } + +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp deleted file mode 100644 index 72c1127a..00000000 --- a/libs/kstd/kstd/vector.test.cpp +++ /dev/null @@ -1,2003 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -SCENARIO("Vector initialization and construction", "[vector]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - - THEN("the size and capacity are zero") - { - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == 0); - } - } - - WHEN("constructing with a specific size") - { - auto v = kstd::vector(10); - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 10); - REQUIRE(v.capacity() == 10); - } - } - - WHEN("constructing from an initializer list") - { - auto v = kstd::vector{1, 2, 3, 4, 5}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 4); - REQUIRE(v[4] == 5); - } - } - } - - GIVEN("A non-empty range") - { - auto range = std::array{1, 2, 3}; - - WHEN("constructing from a random-access iterator range") - { - auto v = kstd::vector{std::begin(range), std::end(range)}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::size(range)); - REQUIRE(v.capacity() == std::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("constructing from a range") - { - auto v = kstd::vector{kstd::from_range, range}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::ranges::size(range)); - REQUIRE(v.capacity() == std::ranges::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A populated vector") - { - auto source = kstd::vector{1, 2, 3, 4, 5}; - - WHEN("copy constructing a new vector") - { - auto copy = kstd::vector(source); - - THEN("the copy matches the original") - { - REQUIRE(copy.size() == source.size()); - REQUIRE(copy.capacity() == source.capacity()); - REQUIRE(copy[0] == 1); - REQUIRE(copy[1] == 2); - REQUIRE(copy[2] == 3); - REQUIRE(copy[3] == 4); - REQUIRE(copy[4] == 5); - } - - THEN("the original is left unchanged") - { - REQUIRE(source.size() == 5); - REQUIRE(source.capacity() == 5); - REQUIRE(source[0] == 1); - REQUIRE(source[1] == 2); - REQUIRE(source[2] == 3); - REQUIRE(source[3] == 4); - REQUIRE(source[4] == 5); - } - } - - WHEN("move constructing a new vector") - { - auto moved = kstd::vector(std::move(source)); - - THEN("The new vector has the original elements") - { - REQUIRE(moved.size() == 5); - REQUIRE(moved.capacity() == 5); - REQUIRE(moved[0] == 1); - REQUIRE(moved[1] == 2); - REQUIRE(moved[2] == 3); - REQUIRE(moved[3] == 4); - REQUIRE(moved[4] == 5); - } - - THEN("The original vector is left in a valid but unspecified state") - { - REQUIRE(source.empty()); - REQUIRE(source.size() == 0); - REQUIRE(source.capacity() == 0); - } - } - } -} - -SCENARIO("Vector element access", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - - WHEN("accessing elements for reading") - { - THEN("operator[] and at() return the correct elements") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - - REQUIRE(v.at(0) == 10); - REQUIRE(v.at(1) == 20); - REQUIRE(v.at(2) == 30); - } - - THEN("front() and back() return the first and last elements") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - } - - THEN("data() return a pointer to the contiguous storage") - { - auto ptr = v.data(); - REQUIRE(ptr); - REQUIRE(ptr[0] == 10); - REQUIRE(ptr[1] == 20); - REQUIRE(ptr[2] == 30); - } - - THEN("accessing out of bounds elements panics") - { - REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); - } - } - - WHEN("accessing elements for writing") - { - v[0] = 100; - v.at(1) = 200; - v.back() = 300; - - THEN("the elements are correctly modified") - { - REQUIRE(v[0] == 100); - REQUIRE(v[1] == 200); - REQUIRE(v[2] == 300); - } - } - } -} - -SCENARIO("Vector iterators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = v.begin(); - REQUIRE(it != v.end()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = v.cbegin(); - REQUIRE(it != v.cend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.cend()); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = v.rbegin(); - REQUIRE(it != v.rend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = v.crbegin(); - REQUIRE(it != v.crend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.crend()); - } - } - } - - GIVEN("an empty vector") - { - auto v = kstd::vector{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(v.begin() == v.end()); - REQUIRE(v.cbegin() == v.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(v.rbegin() == v.rend()); - REQUIRE(v.crbegin() == v.crend()); - } - } - } -} - -SCENARIO("Vector capacity management", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("reserving space") - { - v.reserve(10); - - THEN("the capacity is at least the reserved amount") - { - REQUIRE(v.capacity() >= 10); - } - - THEN("the size is still zero") - { - REQUIRE(v.size() == 0); - } - - THEN("the vector is still empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("reserving space less than or equal to current capacity") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.reserve(5); - - THEN("the capacity remains unchanged") - { - REQUIRE(v.capacity() == current_capacity); - } - } - - WHEN("reserving space greater than max_size") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); - } - } - } - - GIVEN("A populated vector with excess capacity") - { - auto v = kstd::vector{1, 2, 3}; - v.reserve(10); - - REQUIRE(v.capacity() == 10); - - WHEN("calling shrink_to_fit") - { - v.shrink_to_fit(); - - THEN("the capacity is reduced to match the size") - { - REQUIRE(v.capacity() == 3); - REQUIRE(v.size() == 3); - } - - THEN("the elements remain unchanged") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector modifiers", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("push_back is called with a value") - { - v.push_back(10); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 10); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(20); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("elements are added while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.push_back(10); - v.emplace_back(20); - - THEN("the elements are added without reallocation") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("emplace is called with the end iterator and constructor arguments") - { - v.emplace(v.end(), 20); - - THEN("the element is appended and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("emplace is called while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.emplace(v.end(), 20); - - THEN("the element is appended without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == capacity); - REQUIRE(v.back() == 20); - } - } - - WHEN("inserting an element") - { - auto it = v.insert(v.cbegin(), 40); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting an lvalue element") - { - auto const value = 40; - auto it = v.insert(v.cbegin(), value); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("appending a range") - { - auto const range = std::views::iota(0, 3); - v.append_range(range); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - } - } - - WHEN("appending from an input range") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("appending from an input range with sufficient capacity") - { - v.reserve(3); - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity stays the same") - { - REQUIRE(v.capacity() == 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("resizing the vector to a greater size") - { - v.resize(3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - v.resize(3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 2); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 2); - } - } - - WHEN("inserting a range at the beginning") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } - - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - auto initial_capacity = v.capacity(); - - WHEN("push_back is called") - { - v.push_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace is called with an iterator and constructor arguments") - { - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("emplace is called with an iterator and sufficient capacity") - { - v.reserve(v.size() + 1); - - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("push_back is called with a reference to an internal element") - { - v.shrink_to_fit(); - auto const original_value = v[0]; - - v.push_back(v[0]); - - THEN("reallocation handles the internal reference safely without dangling") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == original_value); - REQUIRE(v.back() == original_value); - } - } - - WHEN("pop_back is called") - { - v.pop_back(); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("clear is called") - { - v.clear(); - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == initial_capacity); - } - } - - WHEN("inserting at the beginning") - { - auto it = v.insert(v.cbegin(), 5); - - THEN("the element is inserted at the front") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 5); - REQUIRE(v[1] == 10); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting in the middle") - { - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is inserted in the middle") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting at the end") - { - auto it = v.insert(v.cend(), 40); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting an lvalue at the end") - { - auto const value = 40; - auto it = v.insert(v.cend(), value); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting when capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is added without reallocation") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the first element") - { - auto it = v.erase(v.cbegin()); - - THEN("the first element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 20); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("erasing a middle element") - { - auto it = v.erase(v.cbegin() + 1); - - THEN("the middle element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - auto it = v.erase(v.cend() - 1); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(it == v.end()); - } - } - - WHEN("erasing the end() iterator") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); - } - } - - WHEN("erasing a range of elements") - { - auto it = v.erase(v.cbegin() + 1, v.cend() - 1); - - THEN("the specified range is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing an empty range") - { - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); - - THEN("the vector is unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("appending a range") - { - auto initial_size = v.size(); - v.append_range(std::views::iota(0, 3)); - - THEN("capacity is increased") - { - REQUIRE(v.capacity() >= initial_capacity); - } - - THEN("size is increased") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 1); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a greater size") - { - auto initial_size = v.size(); - v.resize(initial_size + 3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 0); - REQUIRE(v[initial_size + 2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - auto initial_size = v.size(); - v.resize(initial_size + 3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 2); - REQUIRE(v[initial_size + 1] == 2); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a smaller size") - { - v.resize(1); - - THEN("the size decreases and the elements are destroyed") - { - REQUIRE(v.size() == 1); - REQUIRE(v[0] == 10); - } - } - - WHEN("inserting an empty range") - { - auto initial_size = v.size(); - auto it = v.insert_range(v.begin(), std::views::empty); - - THEN("the size does not change") - { - REQUIRE(v.size() == initial_size); - } - - THEN("the capacity does not change") - { - REQUIRE(v.capacity() == initial_capacity); - } - - THEN("the content is unchanged") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - } - - THEN("the returned iterator points to the position of insertion") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the beginning") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 1); - REQUIRE(v[4] == 2); - REQUIRE(v[5] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + initial_size); - } - } - - WHEN("inserting a range that causes reallocation") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - REQUIRE(v[3] == 3); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without reallocation") - { - v.reserve(10); - auto const capacity = v.capacity(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("no reallocation occurs") - { - REQUIRE(v.capacity() == capacity); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without sufficient capacity") - { - v.shrink_to_fit(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 6); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 6); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } -} - -SCENARIO("Vector comparison", "[vector]") -{ - GIVEN("Two identical vectors") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3}; - - WHEN("comparing for equality") - { - THEN("the vectors are equal") - { - REQUIRE(v1 == v2); - } - - THEN("the vectors and not not-equal") - { - REQUIRE_FALSE(v1 != v2); - } - } - - WHEN("comparing using the spaceship operator") - { - THEN("the vectors are equivalent") - { - REQUIRE((v1 <=> v2) == 0); - REQUIRE(v1 <= v2); - REQUIRE(v1 >= v2); - } - } - } - - GIVEN("Two vectors of different sizes") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3, 4}; - - WHEN("comparing for equality") - { - THEN("the vectors are not equal") - { - REQUIRE_FALSE(v1 == v2); - } - - THEN("the vectors are not-equal") - { - REQUIRE(v1 != v2); - } - } - - WHEN("comparing for ordering") - { - THEN("the shorter vector evaluates as less than the longer vector") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } - - GIVEN("Two vectors of the same size but different elements") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 4}; - - WHEN("comparing for ordering") - { - THEN("they are ordered lexicographically") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } -} - -SCENARIO("Vector with non-default-constructible types", "[vector]") -{ - GIVEN("A type without a default constructor") - { - WHEN("constructing an empty vector") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("using emplace_back") - { - auto v = kstd::vector{}; - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); - } - } - } -} - -SCENARIO("Vector with custom allocator", "[vector]") -{ - GIVEN("a tracking allocator acting as the vector's memory manager") - { - auto allocations = 0; - auto allocator = kstd::tests::tracking_allocator{&allocations}; - - WHEN("a vector uses this allocator to allocate memory") - { - auto v = kstd::vector>(allocator); - REQUIRE(allocations == 0); - - v.reserve(10); - - THEN("the allocator was used to allocate memory") - { - REQUIRE(allocations > 0); - } - } - } -} - -SCENARIO("Vector modifier move semantics", "[vector]") -{ - GIVEN("An empty vector and a move tracker element") - { - auto v = kstd::vector{}; - auto tracker = kstd::tests::special_member_tracker{40}; - - WHEN("push_back is called with the move tracker") - { - v.push_back(std::move(tracker)); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 1); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - - THEN("the original tracker is left in a valid but unspecified state") - { - REQUIRE(tracker.value == -1); - } - } - } - - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is constructed directly, without moves or copies") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 0); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - } - } - - GIVEN("A populated vector of move trackers") - { - auto v = kstd::vector{}; - v.reserve(10); - v.emplace_back(10); - v.emplace_back(20); - v.emplace_back(30); - - WHEN("inserting an element in the middle with sufficient capacity") - { - auto const tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, tracker); - - THEN("the shifted elements are move-assigned and the new element is copy-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 1); - REQUIRE(tracker.copied_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("inserting an rvalue element in the middle with sufficient capacity") - { - auto tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, std::move(tracker)); - - THEN("the shifted elements are move-assigned and the new element is move-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - REQUIRE(tracker.moved_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("erasing an element in the middle") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1); - - THEN("the subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 30); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 1); - - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cend() - 1); - - THEN("no elements are moved, just the last element destroyed") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 20); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 0); - REQUIRE(v.data()[2].copied_from_count == 0); - - REQUIRE(it == v.end()); - } - } - - WHEN("erasing a range of elements in the middle") - { - v.emplace_back(40); - v.emplace_back(50); - - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); - - THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 3); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 40); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v[2].value == 50); - REQUIRE(v[2].move_constructed_count == 0); - REQUIRE(v[2].copy_constructed_count == 0); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - - REQUIRE(v.data()[3].destroyed_count == 1); - REQUIRE(v.data()[3].moved_from_count == 1); - REQUIRE(v.data()[3].copied_from_count == 0); - - REQUIRE(v.data()[4].destroyed_count == 1); - REQUIRE(v.data()[4].moved_from_count == 1); - REQUIRE(v.data()[4].copied_from_count == 0); - - REQUIRE(it == v.begin() + 1); - } - } - } -} - -SCENARIO("Vector advanced construction", "[vector]") -{ - GIVEN("A count and a default value") - { - WHEN("constructing with count and default argument") - { - auto const count = 5uz; - auto const value = 42; - auto v = kstd::vector(count, value); - - THEN("the vector is initialized with count copies of value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - REQUIRE(v.front() == 42); - REQUIRE(v.back() == 42); - } - } - } - - GIVEN("A pure input iterator range") - { - WHEN("constructing from input iterators") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - auto v = kstd::vector(first, last); - - THEN("the vector is generated dynamically and initialized correctly") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A tracking allocator and a source vector") - { - auto allocs = 0; - auto allocator = kstd::tests::tracking_allocator{&allocs}; - auto source = kstd::vector>{allocator}; - source.push_back(1); - source.push_back(2); - source.push_back(3); - - allocs = 0; - - WHEN("copy constructing with an allocator") - { - auto copy = kstd::vector>(source, allocator); - - THEN("the copy succeeds and the allocator is used") - { - REQUIRE(copy.size() == 3); - REQUIRE(allocs > 0); - REQUIRE(copy[0] == 1); - REQUIRE(copy[2] == 3); - } - } - - WHEN("move constructing with an identically comparing allocator") - { - auto moved = kstd::vector>(std::move(source), allocator); - - THEN("the move succeeds and no new allocations are made (memory is stolen)") - { - REQUIRE(moved.size() == 3); - REQUIRE(allocs == 0); - REQUIRE(moved[0] == 1); - REQUIRE(moved[2] == 3); - } - } - - WHEN("move constructing with a non-equal allocator") - { - auto allocs2 = 0; - auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; - - auto moved = kstd::vector>(std::move(source), allocator2); - - THEN("the move allocates new memory and moves elements") - { - REQUIRE(allocs2 > 0); - REQUIRE(moved.size() == 3); - REQUIRE(source.empty()); - } - } - } -} - -SCENARIO("Vector assignment operators", "[vector]") -{ - GIVEN("A source vector and an empty target vector") - { - auto source = kstd::vector{1, 2, 3}; - auto target = kstd::vector{}; - - WHEN("copy assigning") - { - target = source; - - THEN("the target matches the source") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.size() == 3); - } - } - - WHEN("move assigning") - { - target = std::move(source); - - THEN("the target assumes the source's data") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.empty()); - } - } - } - - GIVEN("Vectors with propagating copy allocator") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; - auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - auto v2 = kstd::vector>{alloc2}; - - WHEN("copy assigning") - { - v2 = v1; - THEN("the allocator propagates") - { - REQUIRE(v2.get_allocator() == v1.get_allocator()); - } - } - } - - GIVEN("Vectors for copy assignment overlap") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{4, 5}; - v2.reserve(10); - - WHEN("copy assigning a larger vector to a smaller one with enough capacity") - { - v2 = v1; - THEN("elements are copied and size is updated") - { - REQUIRE(v2.size() == 3); - REQUIRE(v2.capacity() >= 10); - REQUIRE(v2[2] == 3); - } - } - - auto v3 = kstd::vector{1, 2, 3, 4}; - v3.reserve(10); - WHEN("copy assigning a smaller vector to a larger one") - { - v3 = v1; - THEN("excess elements are destroyed") - { - REQUIRE(v3.size() == 3); - REQUIRE(v3[0] == 1); - } - } - } - - GIVEN("Vectors with the same tracking allocator") - { - auto allocs = 0; - auto alloc = kstd::tests::tracking_allocator{&allocs}; - auto v1 = kstd::vector>{alloc}; - v1.push_back(1); - auto v2 = kstd::vector>{alloc}; - - WHEN("move assigning") - { - v2 = std::move(v1); - THEN("memory is stolen without allocation") - { - REQUIRE(v2.size() == 1); - REQUIRE(allocs == 1); - } - } - } - - GIVEN("Vectors with different non-propagating tracking allocators") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; - auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - - auto v2 = kstd::vector>{alloc2}; - v2.push_back(4); - v2.push_back(5); - - WHEN("move assigning a larger vector to a smaller one without enough capacity") - { - v2.shrink_to_fit(); - v2 = std::move(v1); - THEN("memory is reallocated and elements are moved") - { - REQUIRE(v2.size() == 3); - REQUIRE(allocs2 > 2); - } - } - - auto v3 = kstd::vector>{alloc2}; - v3.reserve(10); - v3.push_back(4); - v3.push_back(5); - WHEN("move assigning a larger vector to a smaller one with enough capacity") - { - v3 = std::move(v1); - THEN("elements are move-assigned over overlap and move-constructed over remainder") - { - REQUIRE(v3.size() == 3); - } - } - - auto v4 = kstd::vector>{alloc2}; - v4.reserve(10); - v4.push_back(4); - v4.push_back(5); - v4.push_back(6); - v4.push_back(7); - WHEN("move assigning a smaller vector to a larger one with enough capacity") - { - v4 = std::move(v1); - THEN("overlap is moved and excess is destroyed") - { - REQUIRE(v4.size() == 3); - } - } - } -} - -SCENARIO("Vector self-assignment operators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - auto const initial_capacity = v.capacity(); - auto const * initial_data = v.data(); - - WHEN("copy assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-assign-overloaded" - v = v; -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - - WHEN("move assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-move" - v = std::move(v); -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector const accessors and copy insertion", "[vector]") -{ - GIVEN("A const populated vector") - { - auto const v = kstd::vector{10, 20, 30}; - - WHEN("calling const accessors") - { - THEN("elements are read correctly as const references") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - REQUIRE(v[1] == 20); - REQUIRE(v.at(1) == 20); - } - } - } - - GIVEN("An empty vector and a const lvalue tracker") - { - auto v = kstd::vector{}; - auto const tracker = kstd::tests::special_member_tracker{42}; - - WHEN("push_back is called with the const lvalue") - { - v.push_back(tracker); - - THEN("the element is gracefully copy-constructed") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - - WHEN("push_back is called with a const lvalue when capacity is sufficient") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.push_back(tracker); - - THEN("the element is copy-constructed without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == current_capacity); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - } -} diff --git a/libs/kstd/kstd/vector.tests.cpp b/libs/kstd/kstd/vector.tests.cpp new file mode 100644 index 00000000..72c1127a --- /dev/null +++ b/libs/kstd/kstd/vector.tests.cpp @@ -0,0 +1,2003 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +SCENARIO("Vector initialization and construction", "[vector]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + + THEN("the size and capacity are zero") + { + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == 0); + } + } + + WHEN("constructing with a specific size") + { + auto v = kstd::vector(10); + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 10); + REQUIRE(v.capacity() == 10); + } + } + + WHEN("constructing from an initializer list") + { + auto v = kstd::vector{1, 2, 3, 4, 5}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 4); + REQUIRE(v[4] == 5); + } + } + } + + GIVEN("A non-empty range") + { + auto range = std::array{1, 2, 3}; + + WHEN("constructing from a random-access iterator range") + { + auto v = kstd::vector{std::begin(range), std::end(range)}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::size(range)); + REQUIRE(v.capacity() == std::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("constructing from a range") + { + auto v = kstd::vector{kstd::from_range, range}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::ranges::size(range)); + REQUIRE(v.capacity() == std::ranges::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A populated vector") + { + auto source = kstd::vector{1, 2, 3, 4, 5}; + + WHEN("copy constructing a new vector") + { + auto copy = kstd::vector(source); + + THEN("the copy matches the original") + { + REQUIRE(copy.size() == source.size()); + REQUIRE(copy.capacity() == source.capacity()); + REQUIRE(copy[0] == 1); + REQUIRE(copy[1] == 2); + REQUIRE(copy[2] == 3); + REQUIRE(copy[3] == 4); + REQUIRE(copy[4] == 5); + } + + THEN("the original is left unchanged") + { + REQUIRE(source.size() == 5); + REQUIRE(source.capacity() == 5); + REQUIRE(source[0] == 1); + REQUIRE(source[1] == 2); + REQUIRE(source[2] == 3); + REQUIRE(source[3] == 4); + REQUIRE(source[4] == 5); + } + } + + WHEN("move constructing a new vector") + { + auto moved = kstd::vector(std::move(source)); + + THEN("The new vector has the original elements") + { + REQUIRE(moved.size() == 5); + REQUIRE(moved.capacity() == 5); + REQUIRE(moved[0] == 1); + REQUIRE(moved[1] == 2); + REQUIRE(moved[2] == 3); + REQUIRE(moved[3] == 4); + REQUIRE(moved[4] == 5); + } + + THEN("The original vector is left in a valid but unspecified state") + { + REQUIRE(source.empty()); + REQUIRE(source.size() == 0); + REQUIRE(source.capacity() == 0); + } + } + } +} + +SCENARIO("Vector element access", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + + WHEN("accessing elements for reading") + { + THEN("operator[] and at() return the correct elements") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + + REQUIRE(v.at(0) == 10); + REQUIRE(v.at(1) == 20); + REQUIRE(v.at(2) == 30); + } + + THEN("front() and back() return the first and last elements") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + } + + THEN("data() return a pointer to the contiguous storage") + { + auto ptr = v.data(); + REQUIRE(ptr); + REQUIRE(ptr[0] == 10); + REQUIRE(ptr[1] == 20); + REQUIRE(ptr[2] == 30); + } + + THEN("accessing out of bounds elements panics") + { + REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); + } + } + + WHEN("accessing elements for writing") + { + v[0] = 100; + v.at(1) = 200; + v.back() = 300; + + THEN("the elements are correctly modified") + { + REQUIRE(v[0] == 100); + REQUIRE(v[1] == 200); + REQUIRE(v[2] == 300); + } + } + } +} + +SCENARIO("Vector iterators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = v.begin(); + REQUIRE(it != v.end()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = v.cbegin(); + REQUIRE(it != v.cend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.cend()); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = v.rbegin(); + REQUIRE(it != v.rend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = v.crbegin(); + REQUIRE(it != v.crend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.crend()); + } + } + } + + GIVEN("an empty vector") + { + auto v = kstd::vector{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(v.begin() == v.end()); + REQUIRE(v.cbegin() == v.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(v.rbegin() == v.rend()); + REQUIRE(v.crbegin() == v.crend()); + } + } + } +} + +SCENARIO("Vector capacity management", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("reserving space") + { + v.reserve(10); + + THEN("the capacity is at least the reserved amount") + { + REQUIRE(v.capacity() >= 10); + } + + THEN("the size is still zero") + { + REQUIRE(v.size() == 0); + } + + THEN("the vector is still empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("reserving space less than or equal to current capacity") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.reserve(5); + + THEN("the capacity remains unchanged") + { + REQUIRE(v.capacity() == current_capacity); + } + } + + WHEN("reserving space greater than max_size") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); + } + } + } + + GIVEN("A populated vector with excess capacity") + { + auto v = kstd::vector{1, 2, 3}; + v.reserve(10); + + REQUIRE(v.capacity() == 10); + + WHEN("calling shrink_to_fit") + { + v.shrink_to_fit(); + + THEN("the capacity is reduced to match the size") + { + REQUIRE(v.capacity() == 3); + REQUIRE(v.size() == 3); + } + + THEN("the elements remain unchanged") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector modifiers", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("push_back is called with a value") + { + v.push_back(10); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 10); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(20); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("elements are added while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.push_back(10); + v.emplace_back(20); + + THEN("the elements are added without reallocation") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("emplace is called with the end iterator and constructor arguments") + { + v.emplace(v.end(), 20); + + THEN("the element is appended and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("emplace is called while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.emplace(v.end(), 20); + + THEN("the element is appended without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == capacity); + REQUIRE(v.back() == 20); + } + } + + WHEN("inserting an element") + { + auto it = v.insert(v.cbegin(), 40); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting an lvalue element") + { + auto const value = 40; + auto it = v.insert(v.cbegin(), value); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("appending a range") + { + auto const range = std::views::iota(0, 3); + v.append_range(range); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + } + } + + WHEN("appending from an input range") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("appending from an input range with sufficient capacity") + { + v.reserve(3); + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity stays the same") + { + REQUIRE(v.capacity() == 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("resizing the vector to a greater size") + { + v.resize(3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + v.resize(3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 2); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 2); + } + } + + WHEN("inserting a range at the beginning") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } + + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + auto initial_capacity = v.capacity(); + + WHEN("push_back is called") + { + v.push_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace is called with an iterator and constructor arguments") + { + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("emplace is called with an iterator and sufficient capacity") + { + v.reserve(v.size() + 1); + + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("push_back is called with a reference to an internal element") + { + v.shrink_to_fit(); + auto const original_value = v[0]; + + v.push_back(v[0]); + + THEN("reallocation handles the internal reference safely without dangling") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == original_value); + REQUIRE(v.back() == original_value); + } + } + + WHEN("pop_back is called") + { + v.pop_back(); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("clear is called") + { + v.clear(); + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == initial_capacity); + } + } + + WHEN("inserting at the beginning") + { + auto it = v.insert(v.cbegin(), 5); + + THEN("the element is inserted at the front") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 5); + REQUIRE(v[1] == 10); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting in the middle") + { + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is inserted in the middle") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting at the end") + { + auto it = v.insert(v.cend(), 40); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting an lvalue at the end") + { + auto const value = 40; + auto it = v.insert(v.cend(), value); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting when capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is added without reallocation") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the first element") + { + auto it = v.erase(v.cbegin()); + + THEN("the first element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 20); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("erasing a middle element") + { + auto it = v.erase(v.cbegin() + 1); + + THEN("the middle element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + auto it = v.erase(v.cend() - 1); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(it == v.end()); + } + } + + WHEN("erasing the end() iterator") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); + } + } + + WHEN("erasing a range of elements") + { + auto it = v.erase(v.cbegin() + 1, v.cend() - 1); + + THEN("the specified range is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing an empty range") + { + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); + + THEN("the vector is unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("appending a range") + { + auto initial_size = v.size(); + v.append_range(std::views::iota(0, 3)); + + THEN("capacity is increased") + { + REQUIRE(v.capacity() >= initial_capacity); + } + + THEN("size is increased") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 1); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a greater size") + { + auto initial_size = v.size(); + v.resize(initial_size + 3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 0); + REQUIRE(v[initial_size + 2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + auto initial_size = v.size(); + v.resize(initial_size + 3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 2); + REQUIRE(v[initial_size + 1] == 2); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a smaller size") + { + v.resize(1); + + THEN("the size decreases and the elements are destroyed") + { + REQUIRE(v.size() == 1); + REQUIRE(v[0] == 10); + } + } + + WHEN("inserting an empty range") + { + auto initial_size = v.size(); + auto it = v.insert_range(v.begin(), std::views::empty); + + THEN("the size does not change") + { + REQUIRE(v.size() == initial_size); + } + + THEN("the capacity does not change") + { + REQUIRE(v.capacity() == initial_capacity); + } + + THEN("the content is unchanged") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + } + + THEN("the returned iterator points to the position of insertion") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the beginning") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 1); + REQUIRE(v[4] == 2); + REQUIRE(v[5] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + initial_size); + } + } + + WHEN("inserting a range that causes reallocation") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + REQUIRE(v[3] == 3); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without reallocation") + { + v.reserve(10); + auto const capacity = v.capacity(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("no reallocation occurs") + { + REQUIRE(v.capacity() == capacity); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without sufficient capacity") + { + v.shrink_to_fit(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 6); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 6); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } +} + +SCENARIO("Vector comparison", "[vector]") +{ + GIVEN("Two identical vectors") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3}; + + WHEN("comparing for equality") + { + THEN("the vectors are equal") + { + REQUIRE(v1 == v2); + } + + THEN("the vectors and not not-equal") + { + REQUIRE_FALSE(v1 != v2); + } + } + + WHEN("comparing using the spaceship operator") + { + THEN("the vectors are equivalent") + { + REQUIRE((v1 <=> v2) == 0); + REQUIRE(v1 <= v2); + REQUIRE(v1 >= v2); + } + } + } + + GIVEN("Two vectors of different sizes") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3, 4}; + + WHEN("comparing for equality") + { + THEN("the vectors are not equal") + { + REQUIRE_FALSE(v1 == v2); + } + + THEN("the vectors are not-equal") + { + REQUIRE(v1 != v2); + } + } + + WHEN("comparing for ordering") + { + THEN("the shorter vector evaluates as less than the longer vector") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } + + GIVEN("Two vectors of the same size but different elements") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 4}; + + WHEN("comparing for ordering") + { + THEN("they are ordered lexicographically") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } +} + +SCENARIO("Vector with non-default-constructible types", "[vector]") +{ + GIVEN("A type without a default constructor") + { + WHEN("constructing an empty vector") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("using emplace_back") + { + auto v = kstd::vector{}; + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); + } + } + } +} + +SCENARIO("Vector with custom allocator", "[vector]") +{ + GIVEN("a tracking allocator acting as the vector's memory manager") + { + auto allocations = 0; + auto allocator = kstd::tests::tracking_allocator{&allocations}; + + WHEN("a vector uses this allocator to allocate memory") + { + auto v = kstd::vector>(allocator); + REQUIRE(allocations == 0); + + v.reserve(10); + + THEN("the allocator was used to allocate memory") + { + REQUIRE(allocations > 0); + } + } + } +} + +SCENARIO("Vector modifier move semantics", "[vector]") +{ + GIVEN("An empty vector and a move tracker element") + { + auto v = kstd::vector{}; + auto tracker = kstd::tests::special_member_tracker{40}; + + WHEN("push_back is called with the move tracker") + { + v.push_back(std::move(tracker)); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 1); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + + THEN("the original tracker is left in a valid but unspecified state") + { + REQUIRE(tracker.value == -1); + } + } + } + + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is constructed directly, without moves or copies") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 0); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + } + } + + GIVEN("A populated vector of move trackers") + { + auto v = kstd::vector{}; + v.reserve(10); + v.emplace_back(10); + v.emplace_back(20); + v.emplace_back(30); + + WHEN("inserting an element in the middle with sufficient capacity") + { + auto const tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, tracker); + + THEN("the shifted elements are move-assigned and the new element is copy-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 1); + REQUIRE(tracker.copied_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("inserting an rvalue element in the middle with sufficient capacity") + { + auto tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, std::move(tracker)); + + THEN("the shifted elements are move-assigned and the new element is move-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + REQUIRE(tracker.moved_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("erasing an element in the middle") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1); + + THEN("the subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 30); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 1); + + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cend() - 1); + + THEN("no elements are moved, just the last element destroyed") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 20); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 0); + REQUIRE(v.data()[2].copied_from_count == 0); + + REQUIRE(it == v.end()); + } + } + + WHEN("erasing a range of elements in the middle") + { + v.emplace_back(40); + v.emplace_back(50); + + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); + + THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 3); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 40); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v[2].value == 50); + REQUIRE(v[2].move_constructed_count == 0); + REQUIRE(v[2].copy_constructed_count == 0); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + + REQUIRE(v.data()[3].destroyed_count == 1); + REQUIRE(v.data()[3].moved_from_count == 1); + REQUIRE(v.data()[3].copied_from_count == 0); + + REQUIRE(v.data()[4].destroyed_count == 1); + REQUIRE(v.data()[4].moved_from_count == 1); + REQUIRE(v.data()[4].copied_from_count == 0); + + REQUIRE(it == v.begin() + 1); + } + } + } +} + +SCENARIO("Vector advanced construction", "[vector]") +{ + GIVEN("A count and a default value") + { + WHEN("constructing with count and default argument") + { + auto const count = 5uz; + auto const value = 42; + auto v = kstd::vector(count, value); + + THEN("the vector is initialized with count copies of value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + REQUIRE(v.front() == 42); + REQUIRE(v.back() == 42); + } + } + } + + GIVEN("A pure input iterator range") + { + WHEN("constructing from input iterators") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + auto v = kstd::vector(first, last); + + THEN("the vector is generated dynamically and initialized correctly") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A tracking allocator and a source vector") + { + auto allocs = 0; + auto allocator = kstd::tests::tracking_allocator{&allocs}; + auto source = kstd::vector>{allocator}; + source.push_back(1); + source.push_back(2); + source.push_back(3); + + allocs = 0; + + WHEN("copy constructing with an allocator") + { + auto copy = kstd::vector>(source, allocator); + + THEN("the copy succeeds and the allocator is used") + { + REQUIRE(copy.size() == 3); + REQUIRE(allocs > 0); + REQUIRE(copy[0] == 1); + REQUIRE(copy[2] == 3); + } + } + + WHEN("move constructing with an identically comparing allocator") + { + auto moved = kstd::vector>(std::move(source), allocator); + + THEN("the move succeeds and no new allocations are made (memory is stolen)") + { + REQUIRE(moved.size() == 3); + REQUIRE(allocs == 0); + REQUIRE(moved[0] == 1); + REQUIRE(moved[2] == 3); + } + } + + WHEN("move constructing with a non-equal allocator") + { + auto allocs2 = 0; + auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; + + auto moved = kstd::vector>(std::move(source), allocator2); + + THEN("the move allocates new memory and moves elements") + { + REQUIRE(allocs2 > 0); + REQUIRE(moved.size() == 3); + REQUIRE(source.empty()); + } + } + } +} + +SCENARIO("Vector assignment operators", "[vector]") +{ + GIVEN("A source vector and an empty target vector") + { + auto source = kstd::vector{1, 2, 3}; + auto target = kstd::vector{}; + + WHEN("copy assigning") + { + target = source; + + THEN("the target matches the source") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.size() == 3); + } + } + + WHEN("move assigning") + { + target = std::move(source); + + THEN("the target assumes the source's data") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.empty()); + } + } + } + + GIVEN("Vectors with propagating copy allocator") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; + auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + auto v2 = kstd::vector>{alloc2}; + + WHEN("copy assigning") + { + v2 = v1; + THEN("the allocator propagates") + { + REQUIRE(v2.get_allocator() == v1.get_allocator()); + } + } + } + + GIVEN("Vectors for copy assignment overlap") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{4, 5}; + v2.reserve(10); + + WHEN("copy assigning a larger vector to a smaller one with enough capacity") + { + v2 = v1; + THEN("elements are copied and size is updated") + { + REQUIRE(v2.size() == 3); + REQUIRE(v2.capacity() >= 10); + REQUIRE(v2[2] == 3); + } + } + + auto v3 = kstd::vector{1, 2, 3, 4}; + v3.reserve(10); + WHEN("copy assigning a smaller vector to a larger one") + { + v3 = v1; + THEN("excess elements are destroyed") + { + REQUIRE(v3.size() == 3); + REQUIRE(v3[0] == 1); + } + } + } + + GIVEN("Vectors with the same tracking allocator") + { + auto allocs = 0; + auto alloc = kstd::tests::tracking_allocator{&allocs}; + auto v1 = kstd::vector>{alloc}; + v1.push_back(1); + auto v2 = kstd::vector>{alloc}; + + WHEN("move assigning") + { + v2 = std::move(v1); + THEN("memory is stolen without allocation") + { + REQUIRE(v2.size() == 1); + REQUIRE(allocs == 1); + } + } + } + + GIVEN("Vectors with different non-propagating tracking allocators") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; + auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + + auto v2 = kstd::vector>{alloc2}; + v2.push_back(4); + v2.push_back(5); + + WHEN("move assigning a larger vector to a smaller one without enough capacity") + { + v2.shrink_to_fit(); + v2 = std::move(v1); + THEN("memory is reallocated and elements are moved") + { + REQUIRE(v2.size() == 3); + REQUIRE(allocs2 > 2); + } + } + + auto v3 = kstd::vector>{alloc2}; + v3.reserve(10); + v3.push_back(4); + v3.push_back(5); + WHEN("move assigning a larger vector to a smaller one with enough capacity") + { + v3 = std::move(v1); + THEN("elements are move-assigned over overlap and move-constructed over remainder") + { + REQUIRE(v3.size() == 3); + } + } + + auto v4 = kstd::vector>{alloc2}; + v4.reserve(10); + v4.push_back(4); + v4.push_back(5); + v4.push_back(6); + v4.push_back(7); + WHEN("move assigning a smaller vector to a larger one with enough capacity") + { + v4 = std::move(v1); + THEN("overlap is moved and excess is destroyed") + { + REQUIRE(v4.size() == 3); + } + } + } +} + +SCENARIO("Vector self-assignment operators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + auto const initial_capacity = v.capacity(); + auto const * initial_data = v.data(); + + WHEN("copy assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-assign-overloaded" + v = v; +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + + WHEN("move assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-move" + v = std::move(v); +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector const accessors and copy insertion", "[vector]") +{ + GIVEN("A const populated vector") + { + auto const v = kstd::vector{10, 20, 30}; + + WHEN("calling const accessors") + { + THEN("elements are read correctly as const references") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + REQUIRE(v[1] == 20); + REQUIRE(v.at(1) == 20); + } + } + } + + GIVEN("An empty vector and a const lvalue tracker") + { + auto v = kstd::vector{}; + auto const tracker = kstd::tests::special_member_tracker{42}; + + WHEN("push_back is called with the const lvalue") + { + v.push_back(tracker); + + THEN("the element is gracefully copy-constructed") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + + WHEN("push_back is called with a const lvalue when capacity is sufficient") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.push_back(tracker); + + THEN("the element is copy-constructed without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == current_capacity); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + } +} -- cgit v1.2.3 From 878af4b049e0236daa02d075e693c9c630d89aca Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 14:33:58 +0200 Subject: cmake: extract test target creation --- libs/acpi/CMakeLists.txt | 27 ++------------------------- libs/kstd/CMakeLists.txt | 30 ++---------------------------- 2 files changed, 4 insertions(+), 53 deletions(-) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 76941dcf..caffa1c9 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -56,6 +56,7 @@ set_target_properties("acpi" PROPERTIES if(BUILD_TESTING) find_package("Catch2") include("Catch") + include("BuildHostTests") find_program(IASL_EXE NAMES "iasl" REQUIRED) @@ -88,37 +89,13 @@ if(BUILD_TESTING) enable_coverage("acpi") endif() - add_executable("acpi_tests") - add_executable("acpi::tests" ALIAS "acpi_tests") + teachos_add_tests("acpi") target_sources("acpi_tests" PRIVATE - "acpi/common/table_header.tests.cpp" - "acpi/data/madt.tests.cpp" - "acpi/data/rsdt.tests.cpp" - "acpi/data/xsdt.tests.cpp" - "acpi/pointers.tests.cpp" - "acpi/test_data/tables.S" ) target_include_directories("acpi_tests" PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/test_data" ) - - target_link_libraries("acpi_tests" PRIVATE - "Catch2::Catch2WithMain" - "acpi::lib" - ) - - set_target_properties("acpi_tests" PROPERTIES - EXCLUDE_FROM_ALL NO - ) - - file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") - - catch_discover_tests("acpi::tests" - REPORTER junit - OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" - OUTPUT_SUFFIX ".xml" - ) endif() diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 2c63f87d..9c68feed 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -69,38 +69,12 @@ endif() if(BUILD_TESTING) find_package("Catch2") include("Catch") + include("BuildHostTests") - add_executable("kstd_tests") - add_executable("kstd::tests" ALIAS "kstd_tests") - - target_sources("kstd_tests" PRIVATE - "kstd/flat_map.tests.cpp" - "kstd/format.tests.cpp" - "kstd/vector.tests.cpp" - "kstd/bits/observer_ptr.tests.cpp" - "kstd/test_support/os_panic.tests.cpp" - "kstd/string.tests.cpp" - ) - - target_link_libraries("kstd_tests" PRIVATE - "Catch2::Catch2WithMain" - "kstd::lib" - ) - - set_target_properties("kstd_tests" PROPERTIES - EXCLUDE_FROM_ALL NO - ) + teachos_add_tests("kstd") if(COMMAND "enable_coverage") enable_coverage("kstd") enable_coverage("kstd_tests") endif() - - file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") - - catch_discover_tests("kstd::tests" - REPORTER junit - OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" - OUTPUT_SUFFIX ".xml" - ) endif() \ No newline at end of file -- cgit v1.2.3 From 294db0b50e917602f9468f3ccfa0a83ef50f22c0 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 16:14:02 +0200 Subject: kstd: begin implementation of system_error --- libs/kstd/kstd/system_error.hpp | 363 ++++++++++++++++++++++++++++++++++ libs/kstd/kstd/system_error.tests.cpp | 55 ++++++ 2 files changed, 418 insertions(+) create mode 100644 libs/kstd/kstd/system_error.hpp create mode 100644 libs/kstd/kstd/system_error.tests.cpp (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp new file mode 100644 index 00000000..221347a8 --- /dev/null +++ b/libs/kstd/kstd/system_error.hpp @@ -0,0 +1,363 @@ +#ifndef KSTD_SYSTEM_ERROR_HPP +#define KSTD_SYSTEM_ERROR_HPP + +#include +#include +#include +#include +#include + +namespace kstd +{ + + struct error_condition; + struct error_code; + + template + requires(std::is_enum_v) + struct is_error_condition_enum : std::false_type + { + }; + + template + constexpr auto inline is_error_condition_enum_v = is_error_condition_enum::value; + + template + concept error_condition_enum = std::is_enum_v && is_error_condition_enum_v; + + template + requires(std::is_enum_v) + struct is_error_code_enum : std::false_type + { + }; + + template + constexpr auto inline is_error_code_enum_v = is_error_code_enum::value; + + template + concept error_code_enum = std::is_enum_v && is_error_code_enum_v; + + struct error_category + { + constexpr error_category() noexcept = default; + + error_category(error_category const &) = delete; + + constexpr virtual ~error_category() = default; + + auto operator=(error_category const &) -> error_category & = delete; + + [[nodiscard]] constexpr auto virtual name() const noexcept -> std::string_view = 0; + + [[nodiscard]] constexpr auto virtual default_error_condition(int value) const noexcept -> error_condition; + + [[nodiscard]] constexpr auto virtual equivalent(int value, error_condition const & other) const noexcept -> bool; + + [[nodiscard]] constexpr auto virtual equivalent(error_code const &, int condition) const noexcept -> bool; + + [[nodiscard]] constexpr auto virtual message(int value) const noexcept -> std::string_view = 0; + + [[nodiscard]] constexpr auto operator==(error_category const & other) const noexcept -> bool + { + return this == &other; + } + + [[nodiscard]] constexpr auto operator<=>(error_category const & other) const noexcept -> std::strong_ordering + { + return std::compare_three_way{}(this, &other); + } + }; + + struct error_condition + { + constexpr error_condition() noexcept; + + constexpr error_condition(int value, error_category const & category) noexcept + : m_value{value} + , m_category{&category} + {} + + template + constexpr error_condition(Enum value) noexcept + : error_condition{make_error_condition(value)} + {} + + template + constexpr auto operator=(Enum value) noexcept -> error_condition & + { + *this = make_error_condition(value); + } + + constexpr auto assign(int value, error_category const & category) noexcept -> void + { + m_value = value; + m_category = &category; + } + + constexpr auto clear() noexcept -> void; + + [[nodiscard]] constexpr auto value() const noexcept -> int + { + return m_value; + } + + [[nodiscard]] constexpr auto category() const noexcept -> error_category const & + { + return *m_category; + } + + [[nodiscard]] constexpr auto message() const noexcept -> std::string_view + { + return m_category->message(m_value); + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_value != 0; + } + + [[nodiscard]] constexpr auto friend operator==(error_condition const & lhs, error_condition const & rhs) noexcept + -> bool + { + return lhs.category() == rhs.category() && lhs.value() == rhs.value(); + } + + [[nodiscard]] constexpr auto friend operator<=>(error_condition const & lhs, error_condition const & rhs) noexcept + -> std::strong_ordering + { + if (auto result = lhs.category() <=> rhs.category(); result != std::strong_ordering::equal) + { + return result; + } + + return lhs.value() <=> rhs.value(); + } + + private: + int m_value{}; + error_category const * m_category{}; + }; + + struct error_code + { + constexpr error_code() noexcept; + + constexpr error_code(int value, error_category const & category) noexcept + : m_value{value} + , m_category{&category} + {} + + template + constexpr error_code(Enum value) noexcept + : error_code{make_error_condition(value)} + {} + + template + constexpr auto operator=(Enum value) noexcept -> error_code & + { + return *this = make_error_code(value); + } + + constexpr auto assign(int value, error_category const & category) noexcept -> void + { + m_value = value; + m_category = &category; + } + + constexpr auto clear() noexcept -> void; + + [[nodiscard]] constexpr auto value() const noexcept -> int + { + return m_value; + } + + [[nodiscard]] constexpr auto category() const noexcept -> error_category const & + { + return *m_category; + } + + [[nodiscard]] constexpr auto default_error_condition() const noexcept -> error_condition + { + return m_category->default_error_condition(m_value); + } + + [[nodiscard]] constexpr auto message() const noexcept -> std::string_view + { + return m_category->message(m_value); + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_value != 0; + } + + [[nodiscard]] constexpr auto friend operator==(error_code const & lhs, error_code const & rhs) noexcept -> bool + { + return lhs.category() == rhs.category() && lhs.value() == rhs.value(); + } + + [[nodiscard]] constexpr auto friend operator<=>(error_code const & lhs, error_code const & rhs) noexcept + -> std::strong_ordering + { + if (auto result = lhs.category() <=> rhs.category(); result != std::strong_ordering::equal) + { + return result; + } + + return lhs.value() <=> rhs.value(); + } + + private: + int m_value{}; + error_category const * m_category{}; + }; + + enum struct errc : std::uint32_t + { + success = 0, + no_such_file_or_directory, + io_error, + not_enough_memory, + permission_denied, + device_or_resource_busy, + invalid_argument, + not_supported, + }; + + template<> + struct is_error_condition_enum : std::true_type + { + }; + + namespace bits + { + struct generic_category_t final : error_category + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override + { + return "generic"; + } + + [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override + { + switch (static_cast(value)) + { + case errc::success: + return "success"; + case errc::no_such_file_or_directory: + return "no such file or directory"; + case errc::io_error: + return "input/output error"; + case errc::not_enough_memory: + return "not enough memory"; + case errc::permission_denied: + return "permission denied"; + case errc::device_or_resource_busy: + return "device or resource busy"; + case errc::invalid_argument: + return "invalid argument"; + case errc::not_supported: + return "not supported"; + default: + return "unknown generic error"; + } + } + } constexpr inline generic_category_instance{}; + + struct system_category_t final : error_category + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override + { + return "system"; + } + + [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override + { + switch (static_cast(value)) + { + case errc::success: + return "success"; + case errc::no_such_file_or_directory: + return "no such file or directory"; + case errc::io_error: + return "input/output error"; + case errc::not_enough_memory: + return "not enough memory"; + case errc::permission_denied: + return "permission denied"; + case errc::device_or_resource_busy: + return "device or resource busy"; + case errc::invalid_argument: + return "invalid argument"; + case errc::not_supported: + return "not supported"; + default: + return "unknown system error"; + } + } + } constexpr inline system_category_instance{}; + } // namespace bits + + constexpr auto inline generic_category() noexcept -> error_category const & + { + return bits::generic_category_instance; + } + + constexpr auto inline system_category() noexcept -> error_category const & + { + return bits::system_category_instance; + } + + constexpr auto inline error_category::default_error_condition(int value) const noexcept -> error_condition + { + return error_condition{value, *this}; + } + + constexpr auto inline error_category::equivalent(int value, error_condition const & other) const noexcept -> bool + { + return default_error_condition(value) == other; + } + + constexpr auto error_category::equivalent(error_code const & code, int condition) const noexcept -> bool + { + return *this == code.category() && code.value() == condition; + } + + constexpr error_condition::error_condition() noexcept + : error_condition{0, generic_category()} + {} + + constexpr auto error_condition::clear() noexcept -> void + { + m_value = 0; + m_category = &generic_category(); + } + + constexpr auto operator==(error_code const & code, error_condition const & condition) noexcept -> bool + { + return code.category().equivalent(code.value(), condition) || + condition.category().equivalent(code, condition.value()); + } + + constexpr error_code::error_code() noexcept + : error_code{0, system_category()} {}; + + constexpr auto error_code::clear() noexcept -> void + { + m_value = 0; + m_category = &system_category(); + } + + [[nodiscard]] constexpr auto inline make_error_code(errc const value) noexcept -> error_code + { + return {static_cast(value), generic_category()}; + } + + [[nodiscard]] constexpr auto inline make_error_condition(errc const value) noexcept -> error_code + { + return {static_cast(value), generic_category()}; + } + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/system_error.tests.cpp b/libs/kstd/kstd/system_error.tests.cpp new file mode 100644 index 00000000..12f952b3 --- /dev/null +++ b/libs/kstd/kstd/system_error.tests.cpp @@ -0,0 +1,55 @@ +#include + +#include + +SCENARIO("Error condition initialization and construction", "[kstd][system_error]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto error = kstd::error_condition{}; + + THEN("the category is equal to generic_category") + { + REQUIRE(error.category() == kstd::generic_category()); + } + + THEN("the value is 0") + { + REQUIRE(error.value() == 0); + } + + THEN("conversion to bool yields false") + { + REQUIRE_FALSE(error); + } + } + } +} + +SCENARIO("Error code initialization and construction", "[kstd][system_error]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto error = kstd::error_code{}; + + THEN("the category is equal to system_category") + { + REQUIRE(error.category() == kstd::system_category()); + } + + THEN("the value is 0") + { + REQUIRE(error.value() == 0); + } + + THEN("conversion to bool yields false") + { + REQUIRE_FALSE(error); + } + } + } +} -- cgit v1.2.3 From 8f2b785f5e19f0c856edea46ad0b4c625365f177 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 21:49:30 +0200 Subject: kstd: add more POSIX error codes to kstd::errc --- libs/kstd/kstd/system_error.hpp | 403 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 367 insertions(+), 36 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 221347a8..df378b5d 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -215,12 +215,127 @@ namespace kstd enum struct errc : std::uint32_t { success = 0, + operation_not_permitted, no_such_file_or_directory, + no_such_process, + interrupted, io_error, + no_such_device_or_address, + argument_list_too_long, + executable_format_error, + bad_file_descriptor, + no_child_process, + resource_unavailable_try_again, not_enough_memory, permission_denied, + bad_address, + not_a_block_device, device_or_resource_busy, + file_exists, + cross_device_link, + no_such_device, + not_a_directory, + is_a_directory, invalid_argument, + too_many_files_open_in_system, + too_many_files_open, + inappropriate_io_control_operation, + text_file_busy, + file_too_large, + no_space_on_device, + invalid_seek, + read_only_file_system, + too_many_links, + broken_pipe, + argument_out_of_range, + result_out_of_range, + resource_deadlock_would_occur, + file_name_too_long, + no_lock_available, + function_not_supported, + directory_not_empty, + too_many_symbolic_link_levels, + no_message = 42, + identifier_removed, + channel_number_out_of_range, + level_2_not_synchronized, + level_3_halted, + level_3_reset, + link_number_out_of_range, + protocol_driver_not_attached, + no_csi_structure_available, + level_2_halted, + invalid_exchange, + invalid_request_descriptor, + exchange_full, + no_anode, + invalid_request_code, + invalid_slot, + bad_font_file_format = 59, + machine_is_not_on_the_network = 64, + package_not_installed, + object_is_remote, + no_link, + advertise_error, + srmount_error, + communication_error_on_send, + protocol_error, + multihop_attempt, + bad_message = 74, + value_too_large, + name_not_unique_on_network, + file_descriptor_in_bad_state, + remote_address_changed, + shared_library_inaccessible, + shared_library_corrupted, + too_many_shared_libraries, + cannot_execute_shared_library = 83, + illegal_byte_sequence, + interrupted_try_again, + too_many_users = 87, + not_a_socket, + destination_address_required, + message_size, + wrong_protocol_type, + no_protocol_option, + protocol_not_supported, + socket_type_not_supported, + operation_not_supported, + protocol_family_not_supported, + address_family_not_supported, + address_in_use, + address_not_available, + network_down, + network_unreachable, + network_reset, + connection_aborted, + connection_reset, + no_buffer_space, + already_connected, + not_connected, + connection_shut_down, + too_many_references, + timed_out, + connection_refused, + host_is_down, + host_unreachable, + connection_already_in_progress, + operation_in_progress, + stale_file_handle, + structure_needs_cleaning, + remote_io_error = 121, + disk_quota_exceeded, + no_medium_found, + wrong_medium_type, + operation_canceled, + required_key_not_available, + key_expired, + key_revoked, + key_rejected, + owner_dead, + state_not_recoverable, + rf_kill, + memory_page_hardware_error, not_supported, }; @@ -242,24 +357,132 @@ namespace kstd { switch (static_cast(value)) { - case errc::success: - return "success"; - case errc::no_such_file_or_directory: - return "no such file or directory"; - case errc::io_error: - return "input/output error"; - case errc::not_enough_memory: - return "not enough memory"; - case errc::permission_denied: - return "permission denied"; - case errc::device_or_resource_busy: - return "device or resource busy"; - case errc::invalid_argument: - return "invalid argument"; - case errc::not_supported: - return "not supported"; - default: - return "unknown generic error"; + // clang-format off + case errc::success: return "success"; + case errc::operation_not_permitted: return "operation not permitted"; + case errc::no_such_file_or_directory: return "no such file or directory"; + case errc::no_such_process: return "no such process"; + case errc::interrupted: return "system call interrupted"; + case errc::io_error: return "input/output error"; + case errc::no_such_device_or_address: return "no such device or address"; + case errc::argument_list_too_long: return "argument list too long"; + case errc::executable_format_error: return "invalid executable format"; + case errc::bad_file_descriptor: return "bad file descriptor"; + case errc::no_child_process: return "no child process"; + case errc::resource_unavailable_try_again: return "resource unavailable, try again"; + case errc::not_enough_memory: return "not enough memory"; + case errc::permission_denied: return "permission denied"; + case errc::bad_address: return "bad address"; + case errc::not_a_block_device: return "not a block device"; + case errc::device_or_resource_busy: return "device or resource busy"; + case errc::file_exists: return "file exists"; + case errc::cross_device_link: return "cross device link"; + case errc::no_such_device: return "no such device"; + case errc::not_a_directory: return "not a directory"; + case errc::is_a_directory: return "is a directory"; + case errc::invalid_argument: return "invalid argument"; + case errc::too_many_files_open_in_system: return "too many files open in system"; + case errc::too_many_files_open: return "too many files open"; + case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; + case errc::text_file_busy: return "text file busy"; + case errc::file_too_large: return "file too large"; + case errc::no_space_on_device: return "no space on device"; + case errc::invalid_seek: return "invalid seek"; + case errc::read_only_file_system: return "read only file system"; + case errc::too_many_links: return "too many links"; + case errc::broken_pipe: return "broken pipe"; + case errc::argument_out_of_range: return "argument out of range"; + case errc::result_out_of_range: return "result out of range"; + case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; + case errc::file_name_too_long: return "file name too long"; + case errc::no_lock_available: return "no lock available"; + case errc::function_not_supported: return "function not supported"; + case errc::directory_not_empty: return "directory not empty"; + case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; + case errc::no_message: return "no message"; + case errc::identifier_removed: return "identifier removed"; + case errc::channel_number_out_of_range: return "channel number out of range"; + case errc::level_2_not_synchronized: return "level 2 not synchronized"; + case errc::level_3_halted: return "level 3 halted"; + case errc::level_3_reset: return "level 3 reset"; + case errc::link_number_out_of_range: return "link number out of range"; + case errc::protocol_driver_not_attached: return "protocol driver not attached"; + case errc::no_csi_structure_available: return "no csi structure available"; + case errc::level_2_halted: return "level 2 halted"; + case errc::invalid_exchange: return "invalid exchange"; + case errc::invalid_request_descriptor: return "invalid request descriptor"; + case errc::exchange_full: return "exchange full"; + case errc::no_anode: return "no anode"; + case errc::invalid_request_code: return "invalid request code"; + case errc::invalid_slot: return "invalid slot"; + case errc::bad_font_file_format: return "bad font file format"; + case errc::machine_is_not_on_the_network: return "machine is not on the network"; + case errc::package_not_installed: return "package not installed"; + case errc::object_is_remote: return "object is remote"; + case errc::no_link: return "no link"; + case errc::advertise_error: return "advertisement error"; + case errc::srmount_error: return "srmount error"; + case errc::communication_error_on_send: return "communication error on send"; + case errc::protocol_error: return "protocol error"; + case errc::multihop_attempt: return "multihop attempt"; + case errc::bad_message: return "bad message"; + case errc::value_too_large: return "value too large"; + case errc::name_not_unique_on_network: return "name not unique on network"; + case errc::file_descriptor_in_bad_state: return "file descriptor in bad state"; + case errc::remote_address_changed: return "remote address changed"; + case errc::shared_library_inaccessible: return "failed to access shared library"; + case errc::shared_library_corrupted: return "shared library corrupted"; + case errc::too_many_shared_libraries: return "attempted to load too many shared libraries"; + case errc::cannot_execute_shared_library: return "cannot execute shared library"; + case errc::illegal_byte_sequence: return "illegal byte sequence"; + case errc::interrupted_try_again: return "system call interrupted, try again"; + case errc::too_many_users: return "too many logged in users"; + case errc::not_a_socket: return "not a socket"; + case errc::destination_address_required: return "destination address required"; + case errc::message_size: return "message size"; + case errc::wrong_protocol_type: return "wrong protocol type"; + case errc::no_protocol_option: return "no protocol option"; + case errc::protocol_not_supported: return "protocol not supported"; + case errc::socket_type_not_supported: return "socket type not supported"; + case errc::operation_not_supported: return "operation not supported"; + case errc::protocol_family_not_supported: return "protocol family not supported"; + case errc::address_family_not_supported: return "address family not supported"; + case errc::address_in_use: return "address in use"; + case errc::address_not_available: return "address not available"; + case errc::network_down: return "network down"; + case errc::network_unreachable: return "network unreachable"; + case errc::network_reset: return "network reset"; + case errc::connection_aborted: return "connection aborted"; + case errc::connection_reset: return "connection reset"; + case errc::no_buffer_space: return "no buffer space"; + case errc::already_connected: return "already connected"; + case errc::not_connected: return "not connected"; + case errc::connection_shut_down: return "connection shut down"; + case errc::too_many_references: return "too many references"; + case errc::timed_out: return "timed out"; + case errc::connection_refused: return "connection refused"; + case errc::host_is_down: return "host is down"; + case errc::host_unreachable: return "host unreachable"; + case errc::connection_already_in_progress: return "connection already in progress"; + case errc::operation_in_progress: return "operation in progress"; + case errc::stale_file_handle: return "stale file handle"; + case errc::structure_needs_cleaning: return "structure needs cleaning"; + case errc::remote_io_error: return "remote input/output error"; + case errc::disk_quota_exceeded: return "disk quota exceeded"; + case errc::no_medium_found: return "no medium found"; + case errc::wrong_medium_type: return "wrong medium type"; + case errc::operation_canceled: return "operation canceled"; + case errc::required_key_not_available: return "required key not available"; + case errc::key_expired: return "key expired"; + case errc::key_revoked: return "key revoked"; + case errc::key_rejected: return "key rejected"; + case errc::owner_dead: return "owner dead"; + case errc::state_not_recoverable: return "state not recoverable"; + case errc::rf_kill: return "operation not possible due to RFkill"; + case errc::memory_page_hardware_error: return "memory page hardware error"; + case errc::not_supported: return "not supported"; + default: return "unknown system error"; + // clang-format on } } } constexpr inline generic_category_instance{}; @@ -275,24 +498,132 @@ namespace kstd { switch (static_cast(value)) { - case errc::success: - return "success"; - case errc::no_such_file_or_directory: - return "no such file or directory"; - case errc::io_error: - return "input/output error"; - case errc::not_enough_memory: - return "not enough memory"; - case errc::permission_denied: - return "permission denied"; - case errc::device_or_resource_busy: - return "device or resource busy"; - case errc::invalid_argument: - return "invalid argument"; - case errc::not_supported: - return "not supported"; - default: - return "unknown system error"; + // clang-format off + case errc::success: return "success"; + case errc::operation_not_permitted: return "operation not permitted"; + case errc::no_such_file_or_directory: return "no such file or directory"; + case errc::no_such_process: return "no such process"; + case errc::interrupted: return "system call interrupted"; + case errc::io_error: return "input/output error"; + case errc::no_such_device_or_address: return "no such device or address"; + case errc::argument_list_too_long: return "argument list too long"; + case errc::executable_format_error: return "invalid executable format"; + case errc::bad_file_descriptor: return "bad file descriptor"; + case errc::no_child_process: return "no child process"; + case errc::resource_unavailable_try_again: return "resource unavailable, try again"; + case errc::not_enough_memory: return "not enough memory"; + case errc::permission_denied: return "permission denied"; + case errc::bad_address: return "bad address"; + case errc::not_a_block_device: return "not a block device"; + case errc::device_or_resource_busy: return "device or resource busy"; + case errc::file_exists: return "file exists"; + case errc::cross_device_link: return "cross device link"; + case errc::no_such_device: return "no such device"; + case errc::not_a_directory: return "not a directory"; + case errc::is_a_directory: return "is a directory"; + case errc::invalid_argument: return "invalid argument"; + case errc::too_many_files_open_in_system: return "too many files open in system"; + case errc::too_many_files_open: return "too many files open"; + case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; + case errc::text_file_busy: return "text file busy"; + case errc::file_too_large: return "file too large"; + case errc::no_space_on_device: return "no space on device"; + case errc::invalid_seek: return "invalid seek"; + case errc::read_only_file_system: return "read only file system"; + case errc::too_many_links: return "too many links"; + case errc::broken_pipe: return "broken pipe"; + case errc::argument_out_of_range: return "argument out of range"; + case errc::result_out_of_range: return "result out of range"; + case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; + case errc::file_name_too_long: return "file name too long"; + case errc::no_lock_available: return "no lock available"; + case errc::function_not_supported: return "function not supported"; + case errc::directory_not_empty: return "directory not empty"; + case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; + case errc::no_message: return "no message"; + case errc::identifier_removed: return "identifier removed"; + case errc::channel_number_out_of_range: return "channel number out of range"; + case errc::level_2_not_synchronized: return "level 2 not synchronized"; + case errc::level_3_halted: return "level 3 halted"; + case errc::level_3_reset: return "level 3 reset"; + case errc::link_number_out_of_range: return "link number out of range"; + case errc::protocol_driver_not_attached: return "protocol driver not attached"; + case errc::no_csi_structure_available: return "no csi structure available"; + case errc::level_2_halted: return "level 2 halted"; + case errc::invalid_exchange: return "invalid exchange"; + case errc::invalid_request_descriptor: return "invalid request descriptor"; + case errc::exchange_full: return "exchange full"; + case errc::no_anode: return "no anode"; + case errc::invalid_request_code: return "invalid request code"; + case errc::invalid_slot: return "invalid slot"; + case errc::bad_font_file_format: return "bad font file format"; + case errc::machine_is_not_on_the_network: return "machine is not on the network"; + case errc::package_not_installed: return "package not installed"; + case errc::object_is_remote: return "object is remote"; + case errc::no_link: return "no link"; + case errc::advertise_error: return "advertisement error"; + case errc::srmount_error: return "srmount error"; + case errc::communication_error_on_send: return "communication error on send"; + case errc::protocol_error: return "protocol error"; + case errc::multihop_attempt: return "multihop attempt"; + case errc::bad_message: return "bad message"; + case errc::value_too_large: return "value too large"; + case errc::name_not_unique_on_network: return "name not unique on network"; + case errc::file_descriptor_in_bad_state: return "file descriptor in bad state"; + case errc::remote_address_changed: return "remote address changed"; + case errc::shared_library_inaccessible: return "failed to access shared library"; + case errc::shared_library_corrupted: return "shared library corrupted"; + case errc::too_many_shared_libraries: return "attempted to load too many shared libraries"; + case errc::cannot_execute_shared_library: return "cannot execute shared library"; + case errc::illegal_byte_sequence: return "illegal byte sequence"; + case errc::interrupted_try_again: return "system call interrupted, try again"; + case errc::too_many_users: return "too many logged in users"; + case errc::not_a_socket: return "not a socket"; + case errc::destination_address_required: return "destination address required"; + case errc::message_size: return "message size"; + case errc::wrong_protocol_type: return "wrong protocol type"; + case errc::no_protocol_option: return "no protocol option"; + case errc::protocol_not_supported: return "protocol not supported"; + case errc::socket_type_not_supported: return "socket type not supported"; + case errc::operation_not_supported: return "operation not supported"; + case errc::protocol_family_not_supported: return "protocol family not supported"; + case errc::address_family_not_supported: return "address family not supported"; + case errc::address_in_use: return "address in use"; + case errc::address_not_available: return "address not available"; + case errc::network_down: return "network down"; + case errc::network_unreachable: return "network unreachable"; + case errc::network_reset: return "network reset"; + case errc::connection_aborted: return "connection aborted"; + case errc::connection_reset: return "connection reset"; + case errc::no_buffer_space: return "no buffer space"; + case errc::already_connected: return "already connected"; + case errc::not_connected: return "not connected"; + case errc::connection_shut_down: return "connection shut down"; + case errc::too_many_references: return "too many references"; + case errc::timed_out: return "timed out"; + case errc::connection_refused: return "connection refused"; + case errc::host_is_down: return "host is down"; + case errc::host_unreachable: return "host unreachable"; + case errc::connection_already_in_progress: return "connection already in progress"; + case errc::operation_in_progress: return "operation in progress"; + case errc::stale_file_handle: return "stale file handle"; + case errc::structure_needs_cleaning: return "structure needs cleaning"; + case errc::remote_io_error: return "remote input/output error"; + case errc::disk_quota_exceeded: return "disk quota exceeded"; + case errc::no_medium_found: return "no medium found"; + case errc::wrong_medium_type: return "wrong medium type"; + case errc::operation_canceled: return "operation canceled"; + case errc::required_key_not_available: return "required key not available"; + case errc::key_expired: return "key expired"; + case errc::key_revoked: return "key revoked"; + case errc::key_rejected: return "key rejected"; + case errc::owner_dead: return "owner dead"; + case errc::state_not_recoverable: return "state not recoverable"; + case errc::rf_kill: return "operation not possible due to RFkill"; + case errc::memory_page_hardware_error: return "memory page hardware error"; + case errc::not_supported: return "not supported"; + default: return "unknown system error"; + // clang-format on } } } constexpr inline system_category_instance{}; -- cgit v1.2.3 From 49635f99dfa20f1cf17a53b24328871e05cc0757 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 11 Jul 2026 21:44:59 +0200 Subject: kstd: only support official POSIX error codes --- libs/kstd/kstd/system_error.hpp | 497 ++++++++++++++-------------------------- 1 file changed, 178 insertions(+), 319 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index df378b5d..5cc16e74 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -214,129 +214,82 @@ namespace kstd enum struct errc : std::uint32_t { - success = 0, - operation_not_permitted, - no_such_file_or_directory, - no_such_process, - interrupted, - io_error, - no_such_device_or_address, + success, + address_family_not_supported, + address_in_use, + address_not_available, + already_connected, argument_list_too_long, - executable_format_error, - bad_file_descriptor, - no_child_process, - resource_unavailable_try_again, - not_enough_memory, - permission_denied, + argument_out_of_domain, bad_address, - not_a_block_device, + bad_file_descriptor, + bad_message, + broken_pipe, + connection_aborted, + connection_already_in_progress, + connection_refused, + connection_reset, + cross_device_link, + destination_address_required, device_or_resource_busy, + directory_not_empty, + executable_format_error, file_exists, - cross_device_link, - no_such_device, - not_a_directory, - is_a_directory, - invalid_argument, - too_many_files_open_in_system, - too_many_files_open, - inappropriate_io_control_operation, - text_file_busy, file_too_large, - no_space_on_device, - invalid_seek, - read_only_file_system, - too_many_links, - broken_pipe, - argument_out_of_range, - result_out_of_range, - resource_deadlock_would_occur, - file_name_too_long, - no_lock_available, + filename_too_long, function_not_supported, - directory_not_empty, - too_many_symbolic_link_levels, - no_message = 42, + host_unreachable, identifier_removed, - channel_number_out_of_range, - level_2_not_synchronized, - level_3_halted, - level_3_reset, - link_number_out_of_range, - protocol_driver_not_attached, - no_csi_structure_available, - level_2_halted, - invalid_exchange, - invalid_request_descriptor, - exchange_full, - no_anode, - invalid_request_code, - invalid_slot, - bad_font_file_format = 59, - machine_is_not_on_the_network = 64, - package_not_installed, - object_is_remote, - no_link, - advertise_error, - srmount_error, - communication_error_on_send, - protocol_error, - multihop_attempt, - bad_message = 74, - value_too_large, - name_not_unique_on_network, - file_descriptor_in_bad_state, - remote_address_changed, - shared_library_inaccessible, - shared_library_corrupted, - too_many_shared_libraries, - cannot_execute_shared_library = 83, illegal_byte_sequence, - interrupted_try_again, - too_many_users = 87, - not_a_socket, - destination_address_required, + inappropriate_io_control_operation, + interrupted, + invalid_argument, + invalid_seek, + io_error, + is_a_directory, message_size, - wrong_protocol_type, - no_protocol_option, - protocol_not_supported, - socket_type_not_supported, - operation_not_supported, - protocol_family_not_supported, - address_family_not_supported, - address_in_use, - address_not_available, network_down, - network_unreachable, network_reset, - connection_aborted, - connection_reset, + network_unreachable, no_buffer_space, - already_connected, + no_child_process, + no_link, + no_lock_available, + no_message, + no_protocol_option, + no_space_on_device, + no_such_device, + no_such_device_or_address, + no_such_file_or_directory, + no_such_process, + not_a_directory, + not_a_socket, not_connected, - connection_shut_down, - too_many_references, - timed_out, - connection_refused, - host_is_down, - host_unreachable, - connection_already_in_progress, - operation_in_progress, - stale_file_handle, - structure_needs_cleaning, - remote_io_error = 121, - disk_quota_exceeded, - no_medium_found, - wrong_medium_type, + not_enough_memory, + not_supported, operation_canceled, - required_key_not_available, - key_expired, - key_revoked, - key_rejected, + operation_in_progress, + operation_not_permitted, + operation_not_supported, + operation_would_block, owner_dead, + permission_denied, + protocol_error, + protocol_not_supported, + read_only_file_system, + resource_deadlock_would_occur, + resource_unavailable_try_again, + result_out_of_range, + socket_type_not_supported, state_not_recoverable, - rf_kill, - memory_page_hardware_error, - not_supported, + text_file_busy, + timed_out, + too_many_files_open, + too_many_files_open_in_system, + too_many_links, + too_many_symbolic_link_levels, + value_too_large, + wrong_protocol_type, }; template<> @@ -359,128 +312,81 @@ namespace kstd { // clang-format off case errc::success: return "success"; - case errc::operation_not_permitted: return "operation not permitted"; - case errc::no_such_file_or_directory: return "no such file or directory"; - case errc::no_such_process: return "no such process"; - case errc::interrupted: return "system call interrupted"; - case errc::io_error: return "input/output error"; - case errc::no_such_device_or_address: return "no such device or address"; + case errc::address_family_not_supported: return "address family not supported"; + case errc::address_in_use: return "address in use"; + case errc::address_not_available: return "address not available"; + case errc::already_connected: return "already connected"; case errc::argument_list_too_long: return "argument list too long"; - case errc::executable_format_error: return "invalid executable format"; - case errc::bad_file_descriptor: return "bad file descriptor"; - case errc::no_child_process: return "no child process"; - case errc::resource_unavailable_try_again: return "resource unavailable, try again"; - case errc::not_enough_memory: return "not enough memory"; - case errc::permission_denied: return "permission denied"; + case errc::argument_out_of_domain: return "argument out of domain"; case errc::bad_address: return "bad address"; - case errc::not_a_block_device: return "not a block device"; + case errc::bad_file_descriptor: return "bad file descriptor"; + case errc::bad_message: return "bad message"; + case errc::broken_pipe: return "broken pipe"; + case errc::connection_aborted: return "connection aborted"; + case errc::connection_already_in_progress: return "connection already in progress"; + case errc::connection_refused: return "connection refused"; + case errc::connection_reset: return "connection reset"; + case errc::cross_device_link: return "cross device link"; + case errc::destination_address_required: return "destination address required"; case errc::device_or_resource_busy: return "device or resource busy"; + case errc::directory_not_empty: return "directory not empty"; + case errc::executable_format_error: return "invalid executable format"; case errc::file_exists: return "file exists"; - case errc::cross_device_link: return "cross device link"; - case errc::no_such_device: return "no such device"; - case errc::not_a_directory: return "not a directory"; - case errc::is_a_directory: return "is a directory"; - case errc::invalid_argument: return "invalid argument"; - case errc::too_many_files_open_in_system: return "too many files open in system"; - case errc::too_many_files_open: return "too many files open"; - case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; - case errc::text_file_busy: return "text file busy"; case errc::file_too_large: return "file too large"; - case errc::no_space_on_device: return "no space on device"; - case errc::invalid_seek: return "invalid seek"; - case errc::read_only_file_system: return "read only file system"; - case errc::too_many_links: return "too many links"; - case errc::broken_pipe: return "broken pipe"; - case errc::argument_out_of_range: return "argument out of range"; - case errc::result_out_of_range: return "result out of range"; - case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; - case errc::file_name_too_long: return "file name too long"; - case errc::no_lock_available: return "no lock available"; + case errc::filename_too_long: return "filename too long"; case errc::function_not_supported: return "function not supported"; - case errc::directory_not_empty: return "directory not empty"; - case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; - case errc::no_message: return "no message"; + case errc::host_unreachable: return "host unreachable"; case errc::identifier_removed: return "identifier removed"; - case errc::channel_number_out_of_range: return "channel number out of range"; - case errc::level_2_not_synchronized: return "level 2 not synchronized"; - case errc::level_3_halted: return "level 3 halted"; - case errc::level_3_reset: return "level 3 reset"; - case errc::link_number_out_of_range: return "link number out of range"; - case errc::protocol_driver_not_attached: return "protocol driver not attached"; - case errc::no_csi_structure_available: return "no csi structure available"; - case errc::level_2_halted: return "level 2 halted"; - case errc::invalid_exchange: return "invalid exchange"; - case errc::invalid_request_descriptor: return "invalid request descriptor"; - case errc::exchange_full: return "exchange full"; - case errc::no_anode: return "no anode"; - case errc::invalid_request_code: return "invalid request code"; - case errc::invalid_slot: return "invalid slot"; - case errc::bad_font_file_format: return "bad font file format"; - case errc::machine_is_not_on_the_network: return "machine is not on the network"; - case errc::package_not_installed: return "package not installed"; - case errc::object_is_remote: return "object is remote"; - case errc::no_link: return "no link"; - case errc::advertise_error: return "advertisement error"; - case errc::srmount_error: return "srmount error"; - case errc::communication_error_on_send: return "communication error on send"; - case errc::protocol_error: return "protocol error"; - case errc::multihop_attempt: return "multihop attempt"; - case errc::bad_message: return "bad message"; - case errc::value_too_large: return "value too large"; - case errc::name_not_unique_on_network: return "name not unique on network"; - case errc::file_descriptor_in_bad_state: return "file descriptor in bad state"; - case errc::remote_address_changed: return "remote address changed"; - case errc::shared_library_inaccessible: return "failed to access shared library"; - case errc::shared_library_corrupted: return "shared library corrupted"; - case errc::too_many_shared_libraries: return "attempted to load too many shared libraries"; - case errc::cannot_execute_shared_library: return "cannot execute shared library"; case errc::illegal_byte_sequence: return "illegal byte sequence"; - case errc::interrupted_try_again: return "system call interrupted, try again"; - case errc::too_many_users: return "too many logged in users"; - case errc::not_a_socket: return "not a socket"; - case errc::destination_address_required: return "destination address required"; + case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; + case errc::interrupted: return "system call interrupted"; + case errc::invalid_argument: return "invalid argument"; + case errc::invalid_seek: return "invalid seek"; + case errc::io_error: return "input/output error"; + case errc::is_a_directory: return "is a directory"; case errc::message_size: return "message size"; - case errc::wrong_protocol_type: return "wrong protocol type"; - case errc::no_protocol_option: return "no protocol option"; - case errc::protocol_not_supported: return "protocol not supported"; - case errc::socket_type_not_supported: return "socket type not supported"; - case errc::operation_not_supported: return "operation not supported"; - case errc::protocol_family_not_supported: return "protocol family not supported"; - case errc::address_family_not_supported: return "address family not supported"; - case errc::address_in_use: return "address in use"; - case errc::address_not_available: return "address not available"; case errc::network_down: return "network down"; - case errc::network_unreachable: return "network unreachable"; case errc::network_reset: return "network reset"; - case errc::connection_aborted: return "connection aborted"; - case errc::connection_reset: return "connection reset"; + case errc::network_unreachable: return "network unreachable"; case errc::no_buffer_space: return "no buffer space"; - case errc::already_connected: return "already connected"; + case errc::no_child_process: return "no child process"; + case errc::no_link: return "no link"; + case errc::no_lock_available: return "no lock available"; + case errc::no_message: return "no message"; + case errc::no_protocol_option: return "no protocol option"; + case errc::no_space_on_device: return "no space on device"; + case errc::no_such_device: return "no such device"; + case errc::no_such_device_or_address: return "no such device or address"; + case errc::no_such_file_or_directory: return "no such file or directory"; + case errc::no_such_process: return "no such process"; + case errc::not_a_directory: return "not a directory"; + case errc::not_a_socket: return "not a socket"; case errc::not_connected: return "not connected"; - case errc::connection_shut_down: return "connection shut down"; - case errc::too_many_references: return "too many references"; - case errc::timed_out: return "timed out"; - case errc::connection_refused: return "connection refused"; - case errc::host_is_down: return "host is down"; - case errc::host_unreachable: return "host unreachable"; - case errc::connection_already_in_progress: return "connection already in progress"; - case errc::operation_in_progress: return "operation in progress"; - case errc::stale_file_handle: return "stale file handle"; - case errc::structure_needs_cleaning: return "structure needs cleaning"; - case errc::remote_io_error: return "remote input/output error"; - case errc::disk_quota_exceeded: return "disk quota exceeded"; - case errc::no_medium_found: return "no medium found"; - case errc::wrong_medium_type: return "wrong medium type"; + case errc::not_enough_memory: return "not enough memory"; + case errc::not_supported: return "not supported"; case errc::operation_canceled: return "operation canceled"; - case errc::required_key_not_available: return "required key not available"; - case errc::key_expired: return "key expired"; - case errc::key_revoked: return "key revoked"; - case errc::key_rejected: return "key rejected"; + case errc::operation_in_progress: return "operation in progress"; + case errc::operation_not_permitted: return "operation not permitted"; + case errc::operation_not_supported: return "operation not supported"; + case errc::operation_would_block: return "operation would block"; case errc::owner_dead: return "owner dead"; + case errc::permission_denied: return "permission denied"; + case errc::protocol_error: return "protocol error"; + case errc::protocol_not_supported: return "protocol not supported"; + case errc::read_only_file_system: return "read only file system"; + case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; + case errc::resource_unavailable_try_again: return "resource unavailable, try again"; + case errc::result_out_of_range: return "result out of range"; + case errc::socket_type_not_supported: return "socket type not supported"; case errc::state_not_recoverable: return "state not recoverable"; - case errc::rf_kill: return "operation not possible due to RFkill"; - case errc::memory_page_hardware_error: return "memory page hardware error"; - case errc::not_supported: return "not supported"; + case errc::text_file_busy: return "text file busy"; + case errc::timed_out: return "timed out"; + case errc::too_many_files_open: return "too many files open"; + case errc::too_many_files_open_in_system: return "too many files open in system"; + case errc::too_many_links: return "too many links"; + case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; + case errc::value_too_large: return "value too large"; + case errc::wrong_protocol_type: return "wrong protocol type"; default: return "unknown system error"; // clang-format on } @@ -500,128 +406,81 @@ namespace kstd { // clang-format off case errc::success: return "success"; - case errc::operation_not_permitted: return "operation not permitted"; - case errc::no_such_file_or_directory: return "no such file or directory"; - case errc::no_such_process: return "no such process"; - case errc::interrupted: return "system call interrupted"; - case errc::io_error: return "input/output error"; - case errc::no_such_device_or_address: return "no such device or address"; + case errc::address_family_not_supported: return "address family not supported"; + case errc::address_in_use: return "address in use"; + case errc::address_not_available: return "address not available"; + case errc::already_connected: return "already connected"; case errc::argument_list_too_long: return "argument list too long"; - case errc::executable_format_error: return "invalid executable format"; - case errc::bad_file_descriptor: return "bad file descriptor"; - case errc::no_child_process: return "no child process"; - case errc::resource_unavailable_try_again: return "resource unavailable, try again"; - case errc::not_enough_memory: return "not enough memory"; - case errc::permission_denied: return "permission denied"; + case errc::argument_out_of_domain: return "argument out of domain"; case errc::bad_address: return "bad address"; - case errc::not_a_block_device: return "not a block device"; + case errc::bad_file_descriptor: return "bad file descriptor"; + case errc::bad_message: return "bad message"; + case errc::broken_pipe: return "broken pipe"; + case errc::connection_aborted: return "connection aborted"; + case errc::connection_already_in_progress: return "connection already in progress"; + case errc::connection_refused: return "connection refused"; + case errc::connection_reset: return "connection reset"; + case errc::cross_device_link: return "cross device link"; + case errc::destination_address_required: return "destination address required"; case errc::device_or_resource_busy: return "device or resource busy"; + case errc::directory_not_empty: return "directory not empty"; + case errc::executable_format_error: return "invalid executable format"; case errc::file_exists: return "file exists"; - case errc::cross_device_link: return "cross device link"; - case errc::no_such_device: return "no such device"; - case errc::not_a_directory: return "not a directory"; - case errc::is_a_directory: return "is a directory"; - case errc::invalid_argument: return "invalid argument"; - case errc::too_many_files_open_in_system: return "too many files open in system"; - case errc::too_many_files_open: return "too many files open"; - case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; - case errc::text_file_busy: return "text file busy"; case errc::file_too_large: return "file too large"; - case errc::no_space_on_device: return "no space on device"; - case errc::invalid_seek: return "invalid seek"; - case errc::read_only_file_system: return "read only file system"; - case errc::too_many_links: return "too many links"; - case errc::broken_pipe: return "broken pipe"; - case errc::argument_out_of_range: return "argument out of range"; - case errc::result_out_of_range: return "result out of range"; - case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; - case errc::file_name_too_long: return "file name too long"; - case errc::no_lock_available: return "no lock available"; + case errc::filename_too_long: return "filename too long"; case errc::function_not_supported: return "function not supported"; - case errc::directory_not_empty: return "directory not empty"; - case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; - case errc::no_message: return "no message"; + case errc::host_unreachable: return "host unreachable"; case errc::identifier_removed: return "identifier removed"; - case errc::channel_number_out_of_range: return "channel number out of range"; - case errc::level_2_not_synchronized: return "level 2 not synchronized"; - case errc::level_3_halted: return "level 3 halted"; - case errc::level_3_reset: return "level 3 reset"; - case errc::link_number_out_of_range: return "link number out of range"; - case errc::protocol_driver_not_attached: return "protocol driver not attached"; - case errc::no_csi_structure_available: return "no csi structure available"; - case errc::level_2_halted: return "level 2 halted"; - case errc::invalid_exchange: return "invalid exchange"; - case errc::invalid_request_descriptor: return "invalid request descriptor"; - case errc::exchange_full: return "exchange full"; - case errc::no_anode: return "no anode"; - case errc::invalid_request_code: return "invalid request code"; - case errc::invalid_slot: return "invalid slot"; - case errc::bad_font_file_format: return "bad font file format"; - case errc::machine_is_not_on_the_network: return "machine is not on the network"; - case errc::package_not_installed: return "package not installed"; - case errc::object_is_remote: return "object is remote"; - case errc::no_link: return "no link"; - case errc::advertise_error: return "advertisement error"; - case errc::srmount_error: return "srmount error"; - case errc::communication_error_on_send: return "communication error on send"; - case errc::protocol_error: return "protocol error"; - case errc::multihop_attempt: return "multihop attempt"; - case errc::bad_message: return "bad message"; - case errc::value_too_large: return "value too large"; - case errc::name_not_unique_on_network: return "name not unique on network"; - case errc::file_descriptor_in_bad_state: return "file descriptor in bad state"; - case errc::remote_address_changed: return "remote address changed"; - case errc::shared_library_inaccessible: return "failed to access shared library"; - case errc::shared_library_corrupted: return "shared library corrupted"; - case errc::too_many_shared_libraries: return "attempted to load too many shared libraries"; - case errc::cannot_execute_shared_library: return "cannot execute shared library"; case errc::illegal_byte_sequence: return "illegal byte sequence"; - case errc::interrupted_try_again: return "system call interrupted, try again"; - case errc::too_many_users: return "too many logged in users"; - case errc::not_a_socket: return "not a socket"; - case errc::destination_address_required: return "destination address required"; + case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; + case errc::interrupted: return "system call interrupted"; + case errc::invalid_argument: return "invalid argument"; + case errc::invalid_seek: return "invalid seek"; + case errc::io_error: return "input/output error"; + case errc::is_a_directory: return "is a directory"; case errc::message_size: return "message size"; - case errc::wrong_protocol_type: return "wrong protocol type"; - case errc::no_protocol_option: return "no protocol option"; - case errc::protocol_not_supported: return "protocol not supported"; - case errc::socket_type_not_supported: return "socket type not supported"; - case errc::operation_not_supported: return "operation not supported"; - case errc::protocol_family_not_supported: return "protocol family not supported"; - case errc::address_family_not_supported: return "address family not supported"; - case errc::address_in_use: return "address in use"; - case errc::address_not_available: return "address not available"; case errc::network_down: return "network down"; - case errc::network_unreachable: return "network unreachable"; case errc::network_reset: return "network reset"; - case errc::connection_aborted: return "connection aborted"; - case errc::connection_reset: return "connection reset"; + case errc::network_unreachable: return "network unreachable"; case errc::no_buffer_space: return "no buffer space"; - case errc::already_connected: return "already connected"; + case errc::no_child_process: return "no child process"; + case errc::no_link: return "no link"; + case errc::no_lock_available: return "no lock available"; + case errc::no_message: return "no message"; + case errc::no_protocol_option: return "no protocol option"; + case errc::no_space_on_device: return "no space on device"; + case errc::no_such_device: return "no such device"; + case errc::no_such_device_or_address: return "no such device or address"; + case errc::no_such_file_or_directory: return "no such file or directory"; + case errc::no_such_process: return "no such process"; + case errc::not_a_directory: return "not a directory"; + case errc::not_a_socket: return "not a socket"; case errc::not_connected: return "not connected"; - case errc::connection_shut_down: return "connection shut down"; - case errc::too_many_references: return "too many references"; - case errc::timed_out: return "timed out"; - case errc::connection_refused: return "connection refused"; - case errc::host_is_down: return "host is down"; - case errc::host_unreachable: return "host unreachable"; - case errc::connection_already_in_progress: return "connection already in progress"; - case errc::operation_in_progress: return "operation in progress"; - case errc::stale_file_handle: return "stale file handle"; - case errc::structure_needs_cleaning: return "structure needs cleaning"; - case errc::remote_io_error: return "remote input/output error"; - case errc::disk_quota_exceeded: return "disk quota exceeded"; - case errc::no_medium_found: return "no medium found"; - case errc::wrong_medium_type: return "wrong medium type"; + case errc::not_enough_memory: return "not enough memory"; + case errc::not_supported: return "not supported"; case errc::operation_canceled: return "operation canceled"; - case errc::required_key_not_available: return "required key not available"; - case errc::key_expired: return "key expired"; - case errc::key_revoked: return "key revoked"; - case errc::key_rejected: return "key rejected"; + case errc::operation_in_progress: return "operation in progress"; + case errc::operation_not_permitted: return "operation not permitted"; + case errc::operation_not_supported: return "operation not supported"; + case errc::operation_would_block: return "operation would block"; case errc::owner_dead: return "owner dead"; + case errc::permission_denied: return "permission denied"; + case errc::protocol_error: return "protocol error"; + case errc::protocol_not_supported: return "protocol not supported"; + case errc::read_only_file_system: return "read only file system"; + case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; + case errc::resource_unavailable_try_again: return "resource unavailable, try again"; + case errc::result_out_of_range: return "result out of range"; + case errc::socket_type_not_supported: return "socket type not supported"; case errc::state_not_recoverable: return "state not recoverable"; - case errc::rf_kill: return "operation not possible due to RFkill"; - case errc::memory_page_hardware_error: return "memory page hardware error"; - case errc::not_supported: return "not supported"; + case errc::text_file_busy: return "text file busy"; + case errc::timed_out: return "timed out"; + case errc::too_many_files_open: return "too many files open"; + case errc::too_many_files_open_in_system: return "too many files open in system"; + case errc::too_many_links: return "too many links"; + case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; + case errc::value_too_large: return "value too large"; + case errc::wrong_protocol_type: return "wrong protocol type"; default: return "unknown system error"; // clang-format on } -- cgit v1.2.3 From 3aa2680cca25ba5f2e490a601461e09c3f081e3e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 11 Jul 2026 22:28:48 +0200 Subject: kstd: simplify errc message translation --- libs/kstd/kstd/system_error.hpp | 252 ++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 164 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 5cc16e74..7618db2e 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -299,6 +299,92 @@ namespace kstd namespace bits { + constexpr auto to_message(errc error) -> std::string_view + { + switch (error) + { + // clang-format off + case errc::success: return "success"; + case errc::address_family_not_supported: return "address family not supported"; + case errc::address_in_use: return "address in use"; + case errc::address_not_available: return "address not available"; + case errc::already_connected: return "already connected"; + case errc::argument_list_too_long: return "argument list too long"; + case errc::argument_out_of_domain: return "argument out of domain"; + case errc::bad_address: return "bad address"; + case errc::bad_file_descriptor: return "bad file descriptor"; + case errc::bad_message: return "bad message"; + case errc::broken_pipe: return "broken pipe"; + case errc::connection_aborted: return "connection aborted"; + case errc::connection_already_in_progress: return "connection already in progress"; + case errc::connection_refused: return "connection refused"; + case errc::connection_reset: return "connection reset"; + case errc::cross_device_link: return "cross device link"; + case errc::destination_address_required: return "destination address required"; + case errc::device_or_resource_busy: return "device or resource busy"; + case errc::directory_not_empty: return "directory not empty"; + case errc::executable_format_error: return "invalid executable format"; + case errc::file_exists: return "file exists"; + case errc::file_too_large: return "file too large"; + case errc::filename_too_long: return "filename too long"; + case errc::function_not_supported: return "function not supported"; + case errc::host_unreachable: return "host unreachable"; + case errc::identifier_removed: return "identifier removed"; + case errc::illegal_byte_sequence: return "illegal byte sequence"; + case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; + case errc::interrupted: return "system call interrupted"; + case errc::invalid_argument: return "invalid argument"; + case errc::invalid_seek: return "invalid seek"; + case errc::io_error: return "input/output error"; + case errc::is_a_directory: return "is a directory"; + case errc::message_size: return "message size"; + case errc::network_down: return "network down"; + case errc::network_reset: return "network reset"; + case errc::network_unreachable: return "network unreachable"; + case errc::no_buffer_space: return "no buffer space"; + case errc::no_child_process: return "no child process"; + case errc::no_link: return "no link"; + case errc::no_lock_available: return "no lock available"; + case errc::no_message: return "no message"; + case errc::no_protocol_option: return "no protocol option"; + case errc::no_space_on_device: return "no space on device"; + case errc::no_such_device: return "no such device"; + case errc::no_such_device_or_address: return "no such device or address"; + case errc::no_such_file_or_directory: return "no such file or directory"; + case errc::no_such_process: return "no such process"; + case errc::not_a_directory: return "not a directory"; + case errc::not_a_socket: return "not a socket"; + case errc::not_connected: return "not connected"; + case errc::not_enough_memory: return "not enough memory"; + case errc::not_supported: return "not supported"; + case errc::operation_canceled: return "operation canceled"; + case errc::operation_in_progress: return "operation in progress"; + case errc::operation_not_permitted: return "operation not permitted"; + case errc::operation_not_supported: return "operation not supported"; + case errc::operation_would_block: return "operation would block"; + case errc::owner_dead: return "owner dead"; + case errc::permission_denied: return "permission denied"; + case errc::protocol_error: return "protocol error"; + case errc::protocol_not_supported: return "protocol not supported"; + case errc::read_only_file_system: return "read only file system"; + case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; + case errc::resource_unavailable_try_again: return "resource unavailable, try again"; + case errc::result_out_of_range: return "result out of range"; + case errc::socket_type_not_supported: return "socket type not supported"; + case errc::state_not_recoverable: return "state not recoverable"; + case errc::text_file_busy: return "text file busy"; + case errc::timed_out: return "timed out"; + case errc::too_many_files_open: return "too many files open"; + case errc::too_many_files_open_in_system: return "too many files open in system"; + case errc::too_many_links: return "too many links"; + case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; + case errc::value_too_large: return "value too large"; + case errc::wrong_protocol_type: return "wrong protocol type"; + default: return "unknown error"; + // clang-format on + } + } + struct generic_category_t final : error_category { [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override @@ -308,88 +394,7 @@ namespace kstd [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override { - switch (static_cast(value)) - { - // clang-format off - case errc::success: return "success"; - case errc::address_family_not_supported: return "address family not supported"; - case errc::address_in_use: return "address in use"; - case errc::address_not_available: return "address not available"; - case errc::already_connected: return "already connected"; - case errc::argument_list_too_long: return "argument list too long"; - case errc::argument_out_of_domain: return "argument out of domain"; - case errc::bad_address: return "bad address"; - case errc::bad_file_descriptor: return "bad file descriptor"; - case errc::bad_message: return "bad message"; - case errc::broken_pipe: return "broken pipe"; - case errc::connection_aborted: return "connection aborted"; - case errc::connection_already_in_progress: return "connection already in progress"; - case errc::connection_refused: return "connection refused"; - case errc::connection_reset: return "connection reset"; - case errc::cross_device_link: return "cross device link"; - case errc::destination_address_required: return "destination address required"; - case errc::device_or_resource_busy: return "device or resource busy"; - case errc::directory_not_empty: return "directory not empty"; - case errc::executable_format_error: return "invalid executable format"; - case errc::file_exists: return "file exists"; - case errc::file_too_large: return "file too large"; - case errc::filename_too_long: return "filename too long"; - case errc::function_not_supported: return "function not supported"; - case errc::host_unreachable: return "host unreachable"; - case errc::identifier_removed: return "identifier removed"; - case errc::illegal_byte_sequence: return "illegal byte sequence"; - case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; - case errc::interrupted: return "system call interrupted"; - case errc::invalid_argument: return "invalid argument"; - case errc::invalid_seek: return "invalid seek"; - case errc::io_error: return "input/output error"; - case errc::is_a_directory: return "is a directory"; - case errc::message_size: return "message size"; - case errc::network_down: return "network down"; - case errc::network_reset: return "network reset"; - case errc::network_unreachable: return "network unreachable"; - case errc::no_buffer_space: return "no buffer space"; - case errc::no_child_process: return "no child process"; - case errc::no_link: return "no link"; - case errc::no_lock_available: return "no lock available"; - case errc::no_message: return "no message"; - case errc::no_protocol_option: return "no protocol option"; - case errc::no_space_on_device: return "no space on device"; - case errc::no_such_device: return "no such device"; - case errc::no_such_device_or_address: return "no such device or address"; - case errc::no_such_file_or_directory: return "no such file or directory"; - case errc::no_such_process: return "no such process"; - case errc::not_a_directory: return "not a directory"; - case errc::not_a_socket: return "not a socket"; - case errc::not_connected: return "not connected"; - case errc::not_enough_memory: return "not enough memory"; - case errc::not_supported: return "not supported"; - case errc::operation_canceled: return "operation canceled"; - case errc::operation_in_progress: return "operation in progress"; - case errc::operation_not_permitted: return "operation not permitted"; - case errc::operation_not_supported: return "operation not supported"; - case errc::operation_would_block: return "operation would block"; - case errc::owner_dead: return "owner dead"; - case errc::permission_denied: return "permission denied"; - case errc::protocol_error: return "protocol error"; - case errc::protocol_not_supported: return "protocol not supported"; - case errc::read_only_file_system: return "read only file system"; - case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; - case errc::resource_unavailable_try_again: return "resource unavailable, try again"; - case errc::result_out_of_range: return "result out of range"; - case errc::socket_type_not_supported: return "socket type not supported"; - case errc::state_not_recoverable: return "state not recoverable"; - case errc::text_file_busy: return "text file busy"; - case errc::timed_out: return "timed out"; - case errc::too_many_files_open: return "too many files open"; - case errc::too_many_files_open_in_system: return "too many files open in system"; - case errc::too_many_links: return "too many links"; - case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; - case errc::value_too_large: return "value too large"; - case errc::wrong_protocol_type: return "wrong protocol type"; - default: return "unknown system error"; - // clang-format on - } + return to_message(static_cast(value)); } } constexpr inline generic_category_instance{}; @@ -402,88 +407,7 @@ namespace kstd [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override { - switch (static_cast(value)) - { - // clang-format off - case errc::success: return "success"; - case errc::address_family_not_supported: return "address family not supported"; - case errc::address_in_use: return "address in use"; - case errc::address_not_available: return "address not available"; - case errc::already_connected: return "already connected"; - case errc::argument_list_too_long: return "argument list too long"; - case errc::argument_out_of_domain: return "argument out of domain"; - case errc::bad_address: return "bad address"; - case errc::bad_file_descriptor: return "bad file descriptor"; - case errc::bad_message: return "bad message"; - case errc::broken_pipe: return "broken pipe"; - case errc::connection_aborted: return "connection aborted"; - case errc::connection_already_in_progress: return "connection already in progress"; - case errc::connection_refused: return "connection refused"; - case errc::connection_reset: return "connection reset"; - case errc::cross_device_link: return "cross device link"; - case errc::destination_address_required: return "destination address required"; - case errc::device_or_resource_busy: return "device or resource busy"; - case errc::directory_not_empty: return "directory not empty"; - case errc::executable_format_error: return "invalid executable format"; - case errc::file_exists: return "file exists"; - case errc::file_too_large: return "file too large"; - case errc::filename_too_long: return "filename too long"; - case errc::function_not_supported: return "function not supported"; - case errc::host_unreachable: return "host unreachable"; - case errc::identifier_removed: return "identifier removed"; - case errc::illegal_byte_sequence: return "illegal byte sequence"; - case errc::inappropriate_io_control_operation: return "inappropriate io control operation"; - case errc::interrupted: return "system call interrupted"; - case errc::invalid_argument: return "invalid argument"; - case errc::invalid_seek: return "invalid seek"; - case errc::io_error: return "input/output error"; - case errc::is_a_directory: return "is a directory"; - case errc::message_size: return "message size"; - case errc::network_down: return "network down"; - case errc::network_reset: return "network reset"; - case errc::network_unreachable: return "network unreachable"; - case errc::no_buffer_space: return "no buffer space"; - case errc::no_child_process: return "no child process"; - case errc::no_link: return "no link"; - case errc::no_lock_available: return "no lock available"; - case errc::no_message: return "no message"; - case errc::no_protocol_option: return "no protocol option"; - case errc::no_space_on_device: return "no space on device"; - case errc::no_such_device: return "no such device"; - case errc::no_such_device_or_address: return "no such device or address"; - case errc::no_such_file_or_directory: return "no such file or directory"; - case errc::no_such_process: return "no such process"; - case errc::not_a_directory: return "not a directory"; - case errc::not_a_socket: return "not a socket"; - case errc::not_connected: return "not connected"; - case errc::not_enough_memory: return "not enough memory"; - case errc::not_supported: return "not supported"; - case errc::operation_canceled: return "operation canceled"; - case errc::operation_in_progress: return "operation in progress"; - case errc::operation_not_permitted: return "operation not permitted"; - case errc::operation_not_supported: return "operation not supported"; - case errc::operation_would_block: return "operation would block"; - case errc::owner_dead: return "owner dead"; - case errc::permission_denied: return "permission denied"; - case errc::protocol_error: return "protocol error"; - case errc::protocol_not_supported: return "protocol not supported"; - case errc::read_only_file_system: return "read only file system"; - case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; - case errc::resource_unavailable_try_again: return "resource unavailable, try again"; - case errc::result_out_of_range: return "result out of range"; - case errc::socket_type_not_supported: return "socket type not supported"; - case errc::state_not_recoverable: return "state not recoverable"; - case errc::text_file_busy: return "text file busy"; - case errc::timed_out: return "timed out"; - case errc::too_many_files_open: return "too many files open"; - case errc::too_many_files_open_in_system: return "too many files open in system"; - case errc::too_many_links: return "too many links"; - case errc::too_many_symbolic_link_levels: return "too many symbolic link levels"; - case errc::value_too_large: return "value too large"; - case errc::wrong_protocol_type: return "wrong protocol type"; - default: return "unknown system error"; - // clang-format on - } + return to_message(static_cast(value)); } } constexpr inline system_category_instance{}; } // namespace bits -- cgit v1.2.3 From f54e09351a967da21f863fd2d3b17ffd24d3ee87 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 12 Jul 2026 15:24:40 +0200 Subject: kernel: integrate kstd system_error facility --- libs/kstd/kstd/system_error.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 7618db2e..f971703b 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -467,7 +467,7 @@ namespace kstd return {static_cast(value), generic_category()}; } - [[nodiscard]] constexpr auto inline make_error_condition(errc const value) noexcept -> error_code + [[nodiscard]] constexpr auto inline make_error_condition(errc const value) noexcept -> error_condition { return {static_cast(value), generic_category()}; } -- cgit v1.2.3 From 48b95e0b46e32f35ea6f16f41ab99286856292bf Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Jul 2026 12:00:39 +0200 Subject: kapi: extend panic formatting --- libs/kstd/kstd/bits/format/formatter/cstring.hpp | 6 ++ libs/kstd/kstd/bits/format/vformat.cpp | 20 ++++++ libs/kstd/kstd/bits/format/vformat.hpp | 44 ++++++++++-- libs/kstd/kstd/format.tests.cpp | 90 ++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/kstd/bits/format/formatter/cstring.hpp index 553c8caf..12cb9c6f 100644 --- a/libs/kstd/kstd/bits/format/formatter/cstring.hpp +++ b/libs/kstd/kstd/bits/format/formatter/cstring.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace kstd @@ -24,6 +25,11 @@ namespace kstd { }; + template + struct formatter : formatter // NOLINT + { + }; + } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/vformat.cpp b/libs/kstd/kstd/bits/format/vformat.cpp index 495f0a10..7210a0b6 100644 --- a/libs/kstd/kstd/bits/format/vformat.cpp +++ b/libs/kstd/kstd/bits/format/vformat.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -221,4 +222,23 @@ namespace kstd::bits::format return m_size; } + auto span_writer::push(std::string_view text) -> void + { + if (m_position < m_span.size() - 1) + { + auto space_left = m_span.size() - m_position; + auto to_copy = std::min(space_left, text.size()); + std::ranges::copy_n(text.begin(), to_copy, m_span.subspan(m_position).begin()); + m_position += to_copy; + } + } + + auto span_writer::push(char character) -> void + { + if (m_position < m_span.size() - 1) + { + m_span[m_position++] = character; + } + } + } // namespace kstd::bits::format \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp index 829e0ea4..63b41ab6 100644 --- a/libs/kstd/kstd/bits/format/vformat.hpp +++ b/libs/kstd/kstd/bits/format/vformat.hpp @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include namespace kstd { @@ -28,7 +30,7 @@ namespace kstd //! @param args The arguments for the format string. auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void; - struct string_writer : output_buffer + struct string_writer final : output_buffer { auto push(std::string_view text) -> void final; auto push(char character) -> void final; @@ -40,7 +42,7 @@ namespace kstd }; //! A buffer that merely records the size of the output written to it. - struct size_recorder : output_buffer + struct size_recorder final : output_buffer { auto push(std::string_view text) -> void final; auto push(char character) -> void final; @@ -52,9 +54,9 @@ namespace kstd }; template Output> - struct iterator_writer : output_buffer + struct iterator_writer final : output_buffer { - explicit iterator_writer(Output iterator) + constexpr explicit iterator_writer(Output iterator) : m_output{iterator} {} @@ -77,6 +79,25 @@ namespace kstd Output m_output{}; }; + struct span_writer final : output_buffer + { + constexpr explicit span_writer(std::span span) + : m_span{span} + {} + + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + [[nodiscard]] constexpr auto position() const noexcept -> std::size_t + { + return m_position; + } + + private: + std::size_t m_position{}; + std::span m_span{}; + }; + } // namespace bits::format //! Format a given string with the provided arguments. @@ -108,6 +129,21 @@ namespace kstd return buffer.iterator(); } + //! Format a given string with the provided arguments. + //! + //! @param span The span to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A pair of the span written to and the next position to write to. + template + auto format_to(std::span span, format_string...> format, + ArgumentTypes &&... args) -> std::pair, std::size_t> + { + auto buffer = bits::format::span_writer{span}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); + return {span, buffer.position()}; + } + //! Determine the number of characters required to format the format string with the given arguments. //! //! @param format The format string. diff --git a/libs/kstd/kstd/format.tests.cpp b/libs/kstd/kstd/format.tests.cpp index d38a8107..a34cf063 100644 --- a/libs/kstd/kstd/format.tests.cpp +++ b/libs/kstd/kstd/format.tests.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -116,6 +117,95 @@ SCENARIO("Formatting to an output iterator", "[format]") } } +SCENARIO("Formatting to a span", "[format]") +{ + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling format without any arguments.") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt); + + THEN("the unmodified string is written to the span") + { + REQUIRE(std::string_view{span.data(), length} == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the span") + { + REQUIRE(std::string_view(span.data(), length) == "This is a test"); + } + } + + WHEN("the span is too small") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt, 1, 2, 3); + + THEN("the written length is equal to the span size") + { + REQUIRE(length == span.size()); + } + + THEN("only part of the unmodified string is written to the span") + { + REQUIRE(std::string_view(span.data(), length) == "This"); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt, 1, true, -100); + + THEN("the formatted string is written to the span") + { + REQUIRE(std::string_view{span.data(), length} == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the span") + { + REQUIRE(std::string_view{span.data(), length} == "Here are some placeholders: 2 false -200"); + } + } + + WHEN("the span is too small") + { + auto buffer = std::array{}; + auto [span, length] = kstd::format_to(buffer, fmt, 2, false, -200, 4, 5, 6); + + THEN("the written length is equal to the span size") + { + REQUIRE(length == span.size()); + } + + THEN("only part of the unmodified string is written to the span") + { + REQUIRE(std::string_view(span.data(), length) == "Here"); + } + } + } +} + SCENARIO("Determining formatted size") { GIVEN("a format string without any placeholders") -- cgit v1.2.3 From 34ffa4de405d053ef4b53d58e1437f2e82d152b1 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Jul 2026 12:18:08 +0200 Subject: kstd: make error_code formattable --- libs/kstd/kstd/format.hpp | 1 + libs/kstd/kstd/system_error.hpp | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/format.hpp b/libs/kstd/kstd/format.hpp index 5ed798db..5d7f4ae6 100644 --- a/libs/kstd/kstd/format.hpp +++ b/libs/kstd/kstd/format.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index f971703b..08725609 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -1,6 +1,8 @@ #ifndef KSTD_SYSTEM_ERROR_HPP #define KSTD_SYSTEM_ERROR_HPP +#include + #include #include #include @@ -472,6 +474,29 @@ namespace kstd return {static_cast(value), generic_category()}; } + template<> + struct formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + if (it != context.end() && *it != '}') + { + bits::format::error("Invalid specifier for string_view."); + } + return it; + } + + auto format(error_code const & error, format_context & context) const -> void + { + context.push(error.category().name()); + context.push(':'); + formatter{}.format(error.value(), context); + context.push(':'); + context.push(error.message()); + } + }; + } // namespace kstd #endif -- cgit v1.2.3 From 3e73968fdad5d476a205be8c86d3b93d5ce7478c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Jul 2026 14:20:45 +0200 Subject: kstd: fix typo in error_code ctor --- libs/kstd/kstd/system_error.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 08725609..2f1e38bf 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -151,7 +151,7 @@ namespace kstd template constexpr error_code(Enum value) noexcept - : error_code{make_error_condition(value)} + : error_code{make_error_code(value)} {} template -- cgit v1.2.3 From b137e22dc32b38cdc254cbd7b3293a00578a12a3 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Jul 2026 23:15:00 +0200 Subject: kstd: add posix-like error constants --- libs/kstd/kstd/posix.hpp | 85 +++++++++++++++ libs/kstd/kstd/system_error.hpp | 222 +++++++++++++++++++++++++--------------- libs/kstd/kstd/unikstd.h | 12 --- 3 files changed, 227 insertions(+), 92 deletions(-) create mode 100644 libs/kstd/kstd/posix.hpp delete mode 100644 libs/kstd/kstd/unikstd.h (limited to 'libs') diff --git a/libs/kstd/kstd/posix.hpp b/libs/kstd/kstd/posix.hpp new file mode 100644 index 00000000..12bd5ec0 --- /dev/null +++ b/libs/kstd/kstd/posix.hpp @@ -0,0 +1,85 @@ +#ifndef KSTD_CERRNO_HPP +#define KSTD_CERRNO_HPP + +namespace kstd::posix +{ + + constexpr auto static inline eafnosupport = 1; + constexpr auto static inline eaddrinuse = 2; + constexpr auto static inline eaddrnotavail = 3; + constexpr auto static inline eisconn = 4; + constexpr auto static inline e2big = 5; + constexpr auto static inline edom = 6; + constexpr auto static inline efault = 7; + constexpr auto static inline ebadf = 8; + constexpr auto static inline ebadmsg = 9; + constexpr auto static inline epipe = 10; + constexpr auto static inline econnaborted = 11; + constexpr auto static inline ealready = 12; + constexpr auto static inline econnrefused = 13; + constexpr auto static inline econnreset = 14; + constexpr auto static inline exdev = 15; + constexpr auto static inline edestaddrreq = 16; + constexpr auto static inline ebusy = 17; + constexpr auto static inline enotempty = 18; + constexpr auto static inline enoexec = 19; + constexpr auto static inline eexist = 20; + constexpr auto static inline efbig = 21; + constexpr auto static inline enametoolong = 22; + constexpr auto static inline enosys = 23; + constexpr auto static inline ehostunreach = 24; + constexpr auto static inline eidrm = 25; + constexpr auto static inline eilseq = 26; + constexpr auto static inline enotty = 27; + constexpr auto static inline eintr = 28; + constexpr auto static inline einval = 29; + constexpr auto static inline espipe = 30; + constexpr auto static inline eio = 31; + constexpr auto static inline eisdir = 32; + constexpr auto static inline emsgsize = 33; + constexpr auto static inline enetdown = 34; + constexpr auto static inline enetreset = 35; + constexpr auto static inline enetunreach = 36; + constexpr auto static inline enobufs = 37; + constexpr auto static inline echild = 38; + constexpr auto static inline enolink = 39; + constexpr auto static inline enolck = 40; + constexpr auto static inline enomsg = 41; + constexpr auto static inline enoprotoopt = 42; + constexpr auto static inline enospc = 43; + constexpr auto static inline enodev = 44; + constexpr auto static inline enxio = 45; + constexpr auto static inline enoent = 46; + constexpr auto static inline esrch = 47; + constexpr auto static inline enotdir = 48; + constexpr auto static inline enotsock = 49; + constexpr auto static inline enotconn = 50; + constexpr auto static inline enomem = 51; + constexpr auto static inline enotsup = 52; + constexpr auto static inline ecanceled = 53; + constexpr auto static inline einprogress = 54; + constexpr auto static inline eperm = 55; + constexpr auto static inline eopnotsupp = 56; + constexpr auto static inline ewouldblock = 57; + constexpr auto static inline eownerdead = 58; + constexpr auto static inline eacces = 59; + constexpr auto static inline eproto = 60; + constexpr auto static inline eprotonosupport = 61; + constexpr auto static inline erofs = 62; + constexpr auto static inline edeadlk = 63; + constexpr auto static inline eagain = 64; + constexpr auto static inline erange = 65; + constexpr auto static inline esocktnosupport = 66; + constexpr auto static inline enotrecoverable = 67; + constexpr auto static inline etxtbsy = 68; + constexpr auto static inline etimedout = 69; + constexpr auto static inline emfile = 70; + constexpr auto static inline enfile = 71; + constexpr auto static inline emlink = 72; + constexpr auto static inline eloop = 73; + constexpr auto static inline eoverflow = 74; + constexpr auto static inline eprototype = 75; + +} // namespace kstd::posix + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 2f1e38bf..77d85172 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -2,6 +2,7 @@ #define KSTD_SYSTEM_ERROR_HPP #include +#include // IWYU pragma: export #include #include @@ -15,30 +16,74 @@ namespace kstd struct error_condition; struct error_code; + //! Determine if a given enumeration type is an error condition enumeration. + //! + //! An enumeration type is an error condition enumeration if it defines generic, system-level error conditions. + //! Specific subsystem error enumerations on the other hand are not generic enough to be considered error conditions + //! and are thus called error codes. + //! + //! @note Types may opt in to being an error condition enumeration by specializing this class template. + //! + //! @tparam CandidateType The type to check. template requires(std::is_enum_v) struct is_error_condition_enum : std::false_type { }; + //! Determine if a given enumeration type is an error condition enumeration. + //! + //! @see is_error_condition_enum + //! + //! @tparam CandidateType The type to check. template constexpr auto inline is_error_condition_enum_v = is_error_condition_enum::value; + //! Determine if a given enumeration type is an error condition enumeration. + //! + //! @see is_error_condition_enum + //! @tparam CandidateType The type to check. template concept error_condition_enum = std::is_enum_v && is_error_condition_enum_v; + //! Determine if a given enumeration type is an error code enumeration. + //! + //! An enumeration type is an error code enumeration if it defines specific, subsystem-level error codes. + //! Generic error conditions are not specific enough to be considered error codes and are thus called error + //! conditions. + //! + //! @note Types may opt in to being an error code enumeration by specializing this class template. + //! + //! @tparam CandidateType The type to check. template requires(std::is_enum_v) struct is_error_code_enum : std::false_type { }; + //! Determine if a given enumeration type is an error code enumeration. + //! + //! @see is_error_code_enum + //! + //! @tparam CandidateType The type to check. template constexpr auto inline is_error_code_enum_v = is_error_code_enum::value; + //! Determine if a given enumeration type is an error code enumeration. + //! + //! @see is_error_code_enum + //! + //! @tparam CandidateType The type to check. template concept error_code_enum = std::is_enum_v && is_error_code_enum_v; + //! A category of error codes or conditions. + //! + //! An error category is a group of related error codes or conditions. Each error code or condition belongs to exactly + //! one error category. Error categories are used to provide additional context and information about the error codes + //! or conditions they contain. They can also be used to compare error codes or conditions from different categories. + //! + //! Subsystems shall defined their own error categories for their error codes, by inheriting from this class. struct error_category { constexpr error_category() noexcept = default; @@ -49,21 +94,42 @@ namespace kstd auto operator=(error_category const &) -> error_category & = delete; + //! Get the name of this error category. [[nodiscard]] constexpr auto virtual name() const noexcept -> std::string_view = 0; + //! Get the default error condition for a given error code value in this category. + //! + //! @param value The error code value to get the default error condition for. + //! @return The default error condition for the given error code value in this category. [[nodiscard]] constexpr auto virtual default_error_condition(int value) const noexcept -> error_condition; + //! Check if a given error code value is equivalent to a given error condition in this category. + //! + //! @param value The error code value to check. + //! @param other The error condition to compare against. + //! @return True if the error code value is equivalent to the error condition in this category, false otherwise. [[nodiscard]] constexpr auto virtual equivalent(int value, error_condition const & other) const noexcept -> bool; - [[nodiscard]] constexpr auto virtual equivalent(error_code const &, int condition) const noexcept -> bool; - + //! Check if a given error code is equivalent to a given error condition in this category. + //! + //! @param code The error code to check. + //! @param condition The error condition to compare against. + //! @return True if the error code is equivalent to the error condition in this category, false otherwise. + [[nodiscard]] constexpr auto virtual equivalent(error_code const & code, int condition) const noexcept -> bool; + + //! Get the message for a given error code value in this category. + //! + //! @param value The error code value to get the message for. + //! @return The message for the given error code value in this category. [[nodiscard]] constexpr auto virtual message(int value) const noexcept -> std::string_view = 0; + //! Check if this error category is equal to another one. [[nodiscard]] constexpr auto operator==(error_category const & other) const noexcept -> bool { return this == &other; } + //! Lexicographically compare this error category to a different one. [[nodiscard]] constexpr auto operator<=>(error_category const & other) const noexcept -> std::strong_ordering { return std::compare_three_way{}(this, &other); @@ -216,82 +282,80 @@ namespace kstd enum struct errc : std::uint32_t { - success, - address_family_not_supported, - address_in_use, - address_not_available, - already_connected, - argument_list_too_long, - argument_out_of_domain, - bad_address, - bad_file_descriptor, - bad_message, - broken_pipe, - connection_aborted, - connection_already_in_progress, - connection_refused, - connection_reset, - cross_device_link, - destination_address_required, - device_or_resource_busy, - directory_not_empty, - executable_format_error, - file_exists, - file_too_large, - filename_too_long, - function_not_supported, - host_unreachable, - identifier_removed, - illegal_byte_sequence, - inappropriate_io_control_operation, - interrupted, - invalid_argument, - invalid_seek, - io_error, - is_a_directory, - message_size, - network_down, - network_reset, - network_unreachable, - no_buffer_space, - no_child_process, - no_link, - no_lock_available, - no_message, - no_protocol_option, - no_space_on_device, - no_such_device, - no_such_device_or_address, - no_such_file_or_directory, - no_such_process, - not_a_directory, - not_a_socket, - not_connected, - not_enough_memory, - not_supported, - operation_canceled, - operation_in_progress, - operation_not_permitted, - operation_not_supported, - operation_would_block, - owner_dead, - permission_denied, - protocol_error, - protocol_not_supported, - read_only_file_system, - resource_deadlock_would_occur, - resource_unavailable_try_again, - result_out_of_range, - socket_type_not_supported, - state_not_recoverable, - text_file_busy, - timed_out, - too_many_files_open, - too_many_files_open_in_system, - too_many_links, - too_many_symbolic_link_levels, - value_too_large, - wrong_protocol_type, + address_family_not_supported = posix::eafnosupport, + address_in_use = posix::eaddrinuse, + address_not_available = posix::eaddrnotavail, + already_connected = posix::eisconn, + argument_list_too_long = posix::e2big, + argument_out_of_domain = posix::edom, + bad_address = posix::efault, + bad_file_descriptor = posix::ebadf, + bad_message = posix::ebadmsg, + broken_pipe = posix::epipe, + connection_aborted = posix::econnaborted, + connection_already_in_progress = posix::ealready, + connection_refused = posix::econnrefused, + connection_reset = posix::econnreset, + cross_device_link = posix::exdev, + destination_address_required = posix::edestaddrreq, + device_or_resource_busy = posix::ebusy, + directory_not_empty = posix::enotempty, + executable_format_error = posix::enoexec, + file_exists = posix::eexist, + file_too_large = posix::efbig, + filename_too_long = posix::enametoolong, + function_not_supported = posix::enosys, + host_unreachable = posix::ehostunreach, + identifier_removed = posix::eidrm, + illegal_byte_sequence = posix::eilseq, + inappropriate_io_control_operation = posix::enotty, + interrupted = posix::eintr, + invalid_argument = posix::einval, + invalid_seek = posix::espipe, + io_error = posix::eio, + is_a_directory = posix::eisdir, + message_size = posix::emsgsize, + network_down = posix::enetdown, + network_reset = posix::enetreset, + network_unreachable = posix::enetunreach, + no_buffer_space = posix::enobufs, + no_child_process = posix::echild, + no_link = posix::enolink, + no_lock_available = posix::enolck, + no_message = posix::enomsg, + no_protocol_option = posix::enoprotoopt, + no_space_on_device = posix::enospc, + no_such_device_or_address = posix::enxio, + no_such_device = posix::enodev, + no_such_file_or_directory = posix::enoent, + no_such_process = posix::esrch, + not_a_directory = posix::enotdir, + not_a_socket = posix::enotsock, + not_connected = posix::enotconn, + not_enough_memory = posix::enomem, + not_supported = posix::enotsup, + operation_canceled = posix::ecanceled, + operation_in_progress = posix::einprogress, + operation_not_permitted = posix::eperm, + operation_not_supported = posix::eopnotsupp, + operation_would_block = posix::ewouldblock, + owner_dead = posix::eownerdead, + permission_denied = posix::eacces, + protocol_error = posix::eproto, + protocol_not_supported = posix::eprotonosupport, + read_only_file_system = posix::erofs, + resource_deadlock_would_occur = posix::edeadlk, + resource_unavailable_try_again = posix::eagain, + result_out_of_range = posix::erange, + state_not_recoverable = posix::enotrecoverable, + text_file_busy = posix::etxtbsy, + timed_out = posix::etimedout, + too_many_files_open_in_system = posix::enfile, + too_many_files_open = posix::emfile, + too_many_links = posix::emlink, + too_many_symbolic_link_levels = posix::eloop, + value_too_large = posix::eoverflow, + wrong_protocol_type = posix::eprototype, }; template<> @@ -306,7 +370,6 @@ namespace kstd switch (error) { // clang-format off - case errc::success: return "success"; case errc::address_family_not_supported: return "address family not supported"; case errc::address_in_use: return "address in use"; case errc::address_not_available: return "address not available"; @@ -372,7 +435,6 @@ namespace kstd case errc::resource_deadlock_would_occur: return "resource deadlock would occur"; case errc::resource_unavailable_try_again: return "resource unavailable, try again"; case errc::result_out_of_range: return "result out of range"; - case errc::socket_type_not_supported: return "socket type not supported"; case errc::state_not_recoverable: return "state not recoverable"; case errc::text_file_busy: return "text file busy"; case errc::timed_out: return "timed out"; diff --git a/libs/kstd/kstd/unikstd.h b/libs/kstd/kstd/unikstd.h deleted file mode 100644 index aa60be63..00000000 --- a/libs/kstd/kstd/unikstd.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef KSTD_UNIKSTD_HPP -#define KSTD_UNIKSTD_HPP - -#include -#include - -namespace kstd -{ - using ssize_t = std::make_signed_t; -} // namespace kstd - -#endif \ No newline at end of file -- cgit v1.2.3 From 1a9acb2a6a085bbb11d71824512913c0a15dbd68 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Jul 2026 23:26:54 +0200 Subject: kstd: artificially inflate test coverage --- libs/kstd/kstd/system_error.tests.cpp | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.tests.cpp b/libs/kstd/kstd/system_error.tests.cpp index 12f952b3..59e11926 100644 --- a/libs/kstd/kstd/system_error.tests.cpp +++ b/libs/kstd/kstd/system_error.tests.cpp @@ -1,6 +1,7 @@ #include #include +#include SCENARIO("Error condition initialization and construction", "[kstd][system_error]") { @@ -53,3 +54,47 @@ SCENARIO("Error code initialization and construction", "[kstd][system_error]") } } } + +SCENARIO("Error code messages", "[kstd][system_error]") +{ + GIVEN("An error code") + { + auto enumerator = GENERATE( + kstd::errc::address_family_not_supported, kstd::errc::address_in_use, kstd::errc::address_not_available, + kstd::errc::already_connected, kstd::errc::argument_list_too_long, kstd::errc::argument_out_of_domain, + kstd::errc::bad_address, kstd::errc::bad_file_descriptor, kstd::errc::bad_message, kstd::errc::broken_pipe, + kstd::errc::connection_aborted, kstd::errc::connection_already_in_progress, kstd::errc::connection_refused, + kstd::errc::connection_reset, kstd::errc::cross_device_link, kstd::errc::destination_address_required, + kstd::errc::device_or_resource_busy, kstd::errc::directory_not_empty, kstd::errc::executable_format_error, + kstd::errc::file_exists, kstd::errc::file_too_large, kstd::errc::filename_too_long, + kstd::errc::function_not_supported, kstd::errc::host_unreachable, kstd::errc::identifier_removed, + kstd::errc::illegal_byte_sequence, kstd::errc::inappropriate_io_control_operation, kstd::errc::interrupted, + kstd::errc::invalid_argument, kstd::errc::invalid_seek, kstd::errc::io_error, kstd::errc::is_a_directory, + kstd::errc::message_size, kstd::errc::network_down, kstd::errc::network_reset, kstd::errc::network_unreachable, + kstd::errc::no_buffer_space, kstd::errc::no_child_process, kstd::errc::no_link, kstd::errc::no_lock_available, + kstd::errc::no_message, kstd::errc::no_protocol_option, kstd::errc::no_space_on_device, + kstd::errc::no_such_device_or_address, kstd::errc::no_such_device, kstd::errc::no_such_file_or_directory, + kstd::errc::no_such_process, kstd::errc::not_a_directory, kstd::errc::not_a_socket, kstd::errc::not_connected, + kstd::errc::not_enough_memory, kstd::errc::not_supported, kstd::errc::operation_canceled, + kstd::errc::operation_in_progress, kstd::errc::operation_not_permitted, kstd::errc::operation_not_supported, + kstd::errc::operation_would_block, kstd::errc::owner_dead, kstd::errc::permission_denied, + kstd::errc::protocol_error, kstd::errc::protocol_not_supported, kstd::errc::read_only_file_system, + kstd::errc::resource_deadlock_would_occur, kstd::errc::resource_unavailable_try_again, + kstd::errc::result_out_of_range, kstd::errc::state_not_recoverable, kstd::errc::text_file_busy, + kstd::errc::timed_out, kstd::errc::too_many_files_open_in_system, kstd::errc::too_many_files_open, + kstd::errc::too_many_links, kstd::errc::too_many_symbolic_link_levels, kstd::errc::value_too_large, + kstd::errc::wrong_protocol_type); + + auto error = kstd::make_error_code(enumerator); + + WHEN("getting the message") + { + auto message = error.message(); + + THEN("the message is not 'unknown error'") + { + REQUIRE(message != "unknown error"); + } + } + } +} -- cgit v1.2.3 From af017fa7983e6730b2f8fb6acee221dd39c02b7a Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 14 Jul 2026 15:27:49 +0200 Subject: kstd: extract result helper alias --- libs/kstd/kstd/result.hpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 libs/kstd/kstd/result.hpp (limited to 'libs') diff --git a/libs/kstd/kstd/result.hpp b/libs/kstd/kstd/result.hpp new file mode 100644 index 00000000..7b027a8d --- /dev/null +++ b/libs/kstd/kstd/result.hpp @@ -0,0 +1,35 @@ +#ifndef KSTD_RESULT_HPP +#define KSTD_RESULT_HPP + +#include + +#include +#include +#include + +namespace kstd +{ + + template + using result = std::expected; + + template + requires(!std::is_void_v) + constexpr auto inline success(SuccessType && value) -> result> + { + return result>{std::in_place, std::forward(value)}; + } + + constexpr auto inline success() -> result + { + return result{std::in_place}; + } + + constexpr auto inline failure(error_code error) -> std::unexpected + { + return std::unexpected(error); + } + +} // namespace kstd + +#endif \ No newline at end of file -- cgit v1.2.3 From 9558074af8ceef4dc2c3b168a0263775f8a377e0 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 14 Jul 2026 19:22:01 +0200 Subject: kstd: flesh out system_error documentation --- libs/kstd/kstd/system_error.hpp | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/system_error.hpp b/libs/kstd/kstd/system_error.hpp index 77d85172..09efb097 100644 --- a/libs/kstd/kstd/system_error.hpp +++ b/libs/kstd/kstd/system_error.hpp @@ -136,60 +136,93 @@ namespace kstd } }; + //! An error condition represents a generic, system-level error. + //! + //! Error conditions are used to represent errors to be tested for. struct error_condition { + //! Construct an error condition with a default value and category. constexpr error_condition() noexcept; + //! Construct an error condition with a specific value and category. + //! + //! @param value The error code value. + //! @param category The error category. constexpr error_condition(int value, error_category const & category) noexcept : m_value{value} , m_category{&category} {} + //! Construct an error condition from an error condition enumeration value. + //! + //! @tparam Enum The error condition enumeration type. + //! @param value The error condition enumeration value. template constexpr error_condition(Enum value) noexcept : error_condition{make_error_condition(value)} {} + //! Assign an error condition enumeration value to this error condition. + //! + //! @tparam Enum The error condition enumeration type. + //! @param value The error condition enumeration value. template constexpr auto operator=(Enum value) noexcept -> error_condition & { *this = make_error_condition(value); } + //! Assign a new value and category to this error condition. + //! + //! @param value The new error code value. + //! @param category The new error category. constexpr auto assign(int value, error_category const & category) noexcept -> void { m_value = value; m_category = &category; } + //! Clear this error condition. + //! + //! This sets the value to 0 and the category to the generic error category. constexpr auto clear() noexcept -> void; + //! Get the raw error code value of this error condition. [[nodiscard]] constexpr auto value() const noexcept -> int { return m_value; } + //! Get the error category of this error condition. [[nodiscard]] constexpr auto category() const noexcept -> error_category const & { return *m_category; } + //! Get the message associated with this error condition based on its value and category. [[nodiscard]] constexpr auto message() const noexcept -> std::string_view { return m_category->message(m_value); } + //! Check if this error condition holds an error. + //! + //! @return @p true iff. this error condition holds a non-zero value, @p false otherwise. [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_value != 0; } + //! Check if two error conditions are equal. + //! + //! Error conditions are considered equal if they hold the same value and reference the same category. [[nodiscard]] constexpr auto friend operator==(error_condition const & lhs, error_condition const & rhs) noexcept -> bool { return lhs.category() == rhs.category() && lhs.value() == rhs.value(); } + //! Lexicographically compare two error conditions. [[nodiscard]] constexpr auto friend operator<=>(error_condition const & lhs, error_condition const & rhs) noexcept -> std::strong_ordering { @@ -206,64 +239,95 @@ namespace kstd error_category const * m_category{}; }; + //! An error code represents a specific, subsystem-level error. + //! + //! Error codes are used to represent errors that are specific to a subsystem. Equivalence between error codes and + //! error conditions can be defined per each error enumeration type. struct error_code { + //! Construct an error code with a default value and category. constexpr error_code() noexcept; + //! Construct an error code with a specific value and category. constexpr error_code(int value, error_category const & category) noexcept : m_value{value} , m_category{&category} {} + //! Construct an error code from an error code enumeration value. + //! + //! @tparam Enum The error code enumeration type. + //! @param value The error code enumeration value. template constexpr error_code(Enum value) noexcept : error_code{make_error_code(value)} {} + //! Assign an error code enumeration value to this error code. + //! + //! The category is chosen based on the enumeration type of the value. + //! @tparam Enum The error code enumeration type. + //! @param value The error code enumeration value. template constexpr auto operator=(Enum value) noexcept -> error_code & { return *this = make_error_code(value); } + //! Assign a new value and category to this error code. + //! + //! @param value The new error code value. + //! @param category The new error code category. constexpr auto assign(int value, error_category const & category) noexcept -> void { m_value = value; m_category = &category; } + //! Clear this error code. constexpr auto clear() noexcept -> void; + //! Get the raw error code value of this error code. [[nodiscard]] constexpr auto value() const noexcept -> int { return m_value; } + //! Get the error category of this error code. [[nodiscard]] constexpr auto category() const noexcept -> error_category const & { return *m_category; } + //! Get and error condition that is equivalent to this error code. [[nodiscard]] constexpr auto default_error_condition() const noexcept -> error_condition { return m_category->default_error_condition(m_value); } + //! Get the message associated with this error code based on its value and category. [[nodiscard]] constexpr auto message() const noexcept -> std::string_view { return m_category->message(m_value); } + //! Check if this error code holds an error. + //! + //! @return @p true iff. this error code holds a non-zero value, @p false otherwise. [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_value != 0; } + //! Check if two error codes are equal. + //! + //! Error codes are considered equal if they hold the same value and reference the same category. [[nodiscard]] constexpr auto friend operator==(error_code const & lhs, error_code const & rhs) noexcept -> bool { return lhs.category() == rhs.category() && lhs.value() == rhs.value(); } + //! Lexicographically compare two error codes. [[nodiscard]] constexpr auto friend operator<=>(error_code const & lhs, error_code const & rhs) noexcept -> std::strong_ordering { @@ -358,6 +422,7 @@ namespace kstd wrong_protocol_type = posix::eprototype, }; + //! Mark the kstd::errc enumeration as an error condition enumeration. template<> struct is_error_condition_enum : std::true_type { @@ -476,11 +541,13 @@ namespace kstd } constexpr inline system_category_instance{}; } // namespace bits + //! Get a reference to the generic error category. constexpr auto inline generic_category() noexcept -> error_category const & { return bits::generic_category_instance; } + //! Get a reference to the system error category. constexpr auto inline system_category() noexcept -> error_category const & { return bits::system_category_instance; @@ -526,16 +593,21 @@ namespace kstd m_category = &system_category(); } + //! Create an error code from an kstd::errc enumeration value. [[nodiscard]] constexpr auto inline make_error_code(errc const value) noexcept -> error_code { return {static_cast(value), generic_category()}; } + //! Create an error condition from an kstd::errc enumeration value. [[nodiscard]] constexpr auto inline make_error_condition(errc const value) noexcept -> error_condition { return {static_cast(value), generic_category()}; } + //! Enable formatting of kstd::error_code + //! + //! There are currently no valid format specifiers for error codes. template<> struct formatter { -- cgit v1.2.3