From 01549be5c53f74df3df1d7f9de3e702ffb906088 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Wed, 18 Feb 2026 10:35:30 +0100 Subject: add multiboot2 module tag, all modules can be iterated --- libs/multiboot2/include/multiboot2/information.hpp | 29 ++++++++++++++++++++++ .../include/multiboot2/information/data.hpp | 10 ++++++++ 2 files changed, 39 insertions(+) (limited to 'libs') diff --git a/libs/multiboot2/include/multiboot2/information.hpp b/libs/multiboot2/include/multiboot2/information.hpp index 0f48835..fbd534c 100644 --- a/libs/multiboot2/include/multiboot2/information.hpp +++ b/libs/multiboot2/include/multiboot2/information.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -106,6 +107,22 @@ namespace multiboot2 } }; + /** + * @copydoc multiboot2::data::module + */ + struct module : vla_tag + { + using vla_tag::vla_tag; + + /** + * @brief The module command line or name. + */ + [[nodiscard]] auto string() const noexcept -> std::string_view + { + return {data(), size()}; + } + }; + struct information_view { using iterator = iterator; @@ -210,6 +227,18 @@ namespace multiboot2 return maybe_memory_map().value(); } + [[nodiscard]] auto modules() const noexcept + { + auto filter_modules = [](auto const & tag) { + return tag.information_id() == module::id; + }; + auto transform_module = [](auto const & tag) { + return module{&tag}; + }; + return std::ranges::subrange(begin(), end()) | std::views::filter(filter_modules) | + std::views::transform(transform_module); + } + private: template [[nodiscard]] constexpr auto get() const noexcept -> std::optional diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/include/multiboot2/information/data.hpp index ccd8fbb..8d53448 100644 --- a/libs/multiboot2/include/multiboot2/information/data.hpp +++ b/libs/multiboot2/include/multiboot2/information/data.hpp @@ -127,6 +127,16 @@ namespace multiboot2 std::uint32_t entry_version; }; + //! A module loaded by the bootloader. + struct module : tag_data + { + //! The physical start address of this module. + std::uint32_t start_address; + + //! The physical end address of this module. + std::uint32_t end_address; + }; + } // namespace data } // namespace multiboot2 -- cgit v1.2.3 From 9d568ef3e785ce3d6028fa60bd59eaac2e85900a Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Wed, 18 Feb 2026 10:40:52 +0100 Subject: add comment where the command line information is --- libs/multiboot2/include/multiboot2/information/data.hpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'libs') diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/include/multiboot2/information/data.hpp index 8d53448..9fa6d5b 100644 --- a/libs/multiboot2/include/multiboot2/information/data.hpp +++ b/libs/multiboot2/include/multiboot2/information/data.hpp @@ -128,6 +128,9 @@ namespace multiboot2 }; //! A module loaded by the bootloader. + //! + //! @note the command line associated with this module is not part of this structure, since it is of variable size + //! and the contained information starts at the end of this structure. struct module : tag_data { //! The physical start address of this module. -- cgit v1.2.3 From 1486620355dc139603cb6be0105f6e742e6fa8dd Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Thu, 26 Feb 2026 11:25:23 +0100 Subject: fix multiboot2 information vla_tag implementation --- libs/multiboot2/include/multiboot2/information/tag.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/multiboot2/include/multiboot2/information/tag.hpp b/libs/multiboot2/include/multiboot2/information/tag.hpp index cd1fc0e..8d90790 100644 --- a/libs/multiboot2/include/multiboot2/information/tag.hpp +++ b/libs/multiboot2/include/multiboot2/information/tag.hpp @@ -172,9 +172,9 @@ namespace multiboot2 return m_vla.data(); } - [[nodiscard]] auto at() const -> const_reference + [[nodiscard]] auto at(std::size_t index) const -> const_reference { - return m_vla.at(); + return m_vla.at(index); } [[nodiscard]] auto operator[](std::size_t index) const noexcept -> const_reference -- cgit v1.2.3 From 62bf2eef72854750c7325d2e2c6e92562a522e16 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Mon, 2 Mar 2026 23:43:09 +0100 Subject: implement memcpy --- libs/kstd/CMakeLists.txt | 2 + libs/kstd/include/kstd/cstring | 19 +++++++ libs/kstd/src/libc/stdlib.cpp | 36 ++++++------- libs/kstd/src/libc/string.cpp | 116 +++++++++++++++++++++++------------------ 4 files changed, 103 insertions(+), 70 deletions(-) create mode 100644 libs/kstd/include/kstd/cstring (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 1f140f6..77b12a9 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -5,6 +5,7 @@ set(KSTD_LIBC_SYMBOLS "abort" "strlen" "memcmp" + "memcpy" ) target_sources("kstd" PRIVATE @@ -33,6 +34,7 @@ target_sources("kstd" PUBLIC "include/kstd/os/print.hpp" "include/kstd/asm_ptr" + "include/kstd/cstring" "include/kstd/format" "include/kstd/memory" "include/kstd/mutex" diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring new file mode 100644 index 0000000..e97ecac --- /dev/null +++ b/libs/kstd/include/kstd/cstring @@ -0,0 +1,19 @@ +#ifndef KSTD_CSTRING +#define KSTD_CSTRING + +#include + +namespace kstd::libc +{ + + extern "C" + { + auto memcpy(void * dest, void const * src, std::size_t size) -> void *; + auto memmove(void * dest, void const * src, std::size_t size) -> void *; + auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t; + auto strlen(char const * string) -> std::size_t; + } + +} // namespace kstd::libc + +#endif \ No newline at end of file diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/src/libc/stdlib.cpp index 4a5c91f..bb40605 100644 --- a/libs/kstd/src/libc/stdlib.cpp +++ b/libs/kstd/src/libc/stdlib.cpp @@ -1,19 +1,19 @@ -#include "kstd/os/error.hpp" - -namespace kstd::libc -{ - - extern "C" - { - [[noreturn]] auto abort() -> void - { - kstd::os::abort(); - } - - [[noreturn, gnu::weak]] auto free(void *) -> void - { - kstd::os::panic("Tried to call free."); - } - } - +#include "kstd/os/error.hpp" + +namespace kstd::libc +{ + + extern "C" + { + [[noreturn]] auto abort() -> void + { + kstd::os::abort(); + } + + [[noreturn, gnu::weak]] auto free(void *) -> void + { + kstd::os::panic("Tried to call free."); + } + } + } // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp index b7d4c6b..4e264f9 100644 --- a/libs/kstd/src/libc/string.cpp +++ b/libs/kstd/src/libc/string.cpp @@ -1,53 +1,65 @@ -#include -#include -#include -#include -#include - -namespace kstd::libc -{ - - extern "C" - { - auto strlen(char const * string) -> std::size_t - { - return std::distance(string, std::ranges::find(string, nullptr, '\0')); - } - - auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t - { - auto left_span = std::span{static_cast(lhs), size}; - auto right_span = std::span{static_cast(rhs), size}; - auto mismatched = std::ranges::mismatch(left_span, right_span); - - if (mismatched.in1 == left_span.end()) - { - return 0; - } - - return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); - } - - auto memmove(void * dest, void const * src, std::size_t size) -> void * - { - auto dest_span = std::span{static_cast(dest), size}; - auto src_span = std::span{static_cast(src), size}; - if (dest < src) - { - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = src_span[i]; - } - } - else - { - for (std::size_t i = size; i > 0; --i) - { - dest_span[i - 1] = src_span[i - 1]; - } - } - return dest; - } - } - +#include + +#include +#include +#include +#include +#include + +namespace kstd::libc +{ + + auto memcpy(void * dest, void const * src, std::size_t size) -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + + return dest; + } + + auto strlen(char const * string) -> std::size_t + { + return std::distance(string, std::ranges::find(string, nullptr, '\0')); + } + + auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t + { + auto left_span = std::span{static_cast(lhs), size}; + auto right_span = std::span{static_cast(rhs), size}; + auto mismatched = std::ranges::mismatch(left_span, right_span); + + if (mismatched.in1 == left_span.end()) + { + return 0; + } + + return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); + } + + auto memmove(void * dest, void const * src, std::size_t size) -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + if (dest < src) + { + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + } + else + { + for (std::size_t i = size; i > 0; --i) + { + dest_span[i - 1] = src_span[i - 1]; + } + } + return dest; + } + } // namespace kstd::libc \ No newline at end of file -- cgit v1.2.3 From f25b3c3cb5fd1e00521b91d575da0177dbb44ddc Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 3 Mar 2026 13:53:47 +0100 Subject: implement memset --- libs/kstd/include/kstd/cstring | 2 ++ libs/kstd/src/libc/string.cpp | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring index e97ecac..06932a4 100644 --- a/libs/kstd/include/kstd/cstring +++ b/libs/kstd/include/kstd/cstring @@ -9,8 +9,10 @@ namespace kstd::libc extern "C" { auto memcpy(void * dest, void const * src, std::size_t size) -> void *; + auto memset(void * dest, std::byte value, std::size_t size) -> void *; auto memmove(void * dest, void const * src, std::size_t size) -> void *; auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t; + auto strlen(char const * string) -> std::size_t; } diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp index 4e264f9..302046d 100644 --- a/libs/kstd/src/libc/string.cpp +++ b/libs/kstd/src/libc/string.cpp @@ -22,9 +22,16 @@ namespace kstd::libc return dest; } - auto strlen(char const * string) -> std::size_t + auto memset(void * dest, std::byte value, std::size_t size) -> void * { - return std::distance(string, std::ranges::find(string, nullptr, '\0')); + auto dest_span = std::span{static_cast(dest), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = value; + } + + return dest; } auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t @@ -62,4 +69,9 @@ namespace kstd::libc return dest; } + auto strlen(char const * string) -> std::size_t + { + return std::distance(string, std::ranges::find(string, nullptr, '\0')); + } + } // namespace kstd::libc \ No newline at end of file -- cgit v1.2.3 From fe0aadec94834b72f4511ce5e300b9fb22e66e60 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 3 Mar 2026 13:57:33 +0100 Subject: small refactoring --- libs/kstd/include/kstd/cstring | 2 +- libs/kstd/src/libc/string.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring index 06932a4..a5c41fa 100644 --- a/libs/kstd/include/kstd/cstring +++ b/libs/kstd/include/kstd/cstring @@ -9,7 +9,7 @@ namespace kstd::libc extern "C" { auto memcpy(void * dest, void const * src, std::size_t size) -> void *; - auto memset(void * dest, std::byte value, std::size_t size) -> void *; + auto memset(void * dest, int value, std::size_t size) -> void *; auto memmove(void * dest, void const * src, std::size_t size) -> void *; auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t; diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp index 302046d..63f012c 100644 --- a/libs/kstd/src/libc/string.cpp +++ b/libs/kstd/src/libc/string.cpp @@ -22,13 +22,14 @@ namespace kstd::libc return dest; } - auto memset(void * dest, std::byte value, std::size_t size) -> void * + auto memset(void * dest, int value, std::size_t size) -> void * { + auto const byte_value = static_cast(static_cast(value)); auto dest_span = std::span{static_cast(dest), size}; for (std::size_t i = 0; i < size; ++i) { - dest_span[i] = value; + dest_span[i] = byte_value; } return dest; -- cgit v1.2.3 From 471888c64ed490b1f1dbaa2c2f67a1e8d315905a Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 17 Mar 2026 11:48:40 +0100 Subject: extend shared_ptr to support nullptr and cross-type conversions --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 131 +++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 6 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index cfe5d18..ed23d29 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -3,12 +3,16 @@ #include #include +#include #include // IWYU pragma: private, include namespace kstd { + template + struct shared_ptr; + /** * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of @@ -24,6 +28,17 @@ namespace kstd template struct shared_ptr { + template + friend struct shared_ptr; + + /** + * @brief Construct an empty shared_ptr from nullptr. + */ + shared_ptr(std::nullptr_t) noexcept + : pointer(nullptr) + , ref_count(nullptr) + {} + /** * @brief Constructor. * @@ -31,7 +46,7 @@ namespace kstd */ explicit shared_ptr(T * pointer = nullptr) : pointer(pointer) - , ref_count(new std::atomic(pointer != nullptr ? 1 : 0)) + , ref_count(pointer != nullptr ? new std::atomic(1) : nullptr) { // Nothing to do. } @@ -45,7 +60,25 @@ namespace kstd : pointer(other.pointer) , ref_count(other.ref_count) { - if (pointer != nullptr) + if (ref_count != nullptr) + { + ++(*ref_count); + } + } + + /** + * @brief Converting copy constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to copy from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr const & other) + : pointer(other.pointer) + , ref_count(other.ref_count) + { + if (ref_count != nullptr) { ++(*ref_count); } @@ -64,6 +97,22 @@ namespace kstd other.ref_count = nullptr; } + /** + * @brief Converting move constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to move from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr && other) noexcept + : pointer(other.pointer) + , ref_count(other.ref_count) + { + other.pointer = nullptr; + other.ref_count = nullptr; + } + /** * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the * object managed by r. If r manages no object, *this manages no object too. Equivalent to @@ -80,7 +129,7 @@ namespace kstd pointer = other.pointer; ref_count = other.ref_count; - if (pointer != nullptr) + if (ref_count != nullptr) { ++(*ref_count); } @@ -89,6 +138,29 @@ namespace kstd return *this; } + /** + * @brief Converting copy assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to share ownership with. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr const & other) -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + ref_count = other.ref_count; + + if (ref_count != nullptr) + { + ++(*ref_count); + } + + return *this; + } + /** * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of * the previous state of r, and r is empty. Equivalent to shared_ptr(std::move(r)).swap(*this). @@ -110,6 +182,37 @@ namespace kstd return *this; } + /** + * @brief Converting move assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to acquire ownership from. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr && other) noexcept -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + ref_count = other.ref_count; + other.pointer = nullptr; + other.ref_count = nullptr; + + return *this; + } + + /** + * @brief Reset this shared_ptr to empty via nullptr assignment. + */ + auto operator=(std::nullptr_t) noexcept -> shared_ptr & + { + cleanup(); + pointer = nullptr; + ref_count = nullptr; + return *this; + } + /** * @brief Destructor. Cleans up resources if necessary. */ @@ -127,7 +230,7 @@ namespace kstd { cleanup(); pointer = ptr; - ref_count = new std::atomic(ptr != nullptr ? 1 : 0); + ref_count = ptr != nullptr ? new std::atomic(1) : nullptr; } /** @@ -185,7 +288,7 @@ namespace kstd */ [[nodiscard]] auto use_count() const -> std::size_t { - if (pointer != nullptr) + if (ref_count != nullptr) { return *ref_count; } @@ -203,6 +306,22 @@ namespace kstd return pointer != nullptr; } + /** + * @brief Compare shared_ptr with nullptr. + */ + [[nodiscard]] auto operator==(std::nullptr_t) const -> bool + { + return pointer == nullptr; + } + + /** + * @brief Compare nullptr with shared_ptr. + */ + friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool + { + return ptr.pointer == nullptr; + } + /** * @brief Defaulted three-way comparator operator. */ @@ -214,7 +333,7 @@ namespace kstd */ auto cleanup() -> void { - if (pointer != nullptr && ref_count != nullptr && --(*ref_count) == 0) + if (ref_count != nullptr && --(*ref_count) == 0) { delete pointer; delete ref_count; -- cgit v1.2.3 From 9ad3eafdfa46dc01d7a8737a59fe59caffd91d67 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 17 Mar 2026 22:22:29 +0100 Subject: kstd: fix constructor selection in vector The old version would lead to potential issues, since an explicit ctor may get selected. Ideally vector should be adapted to not allocated an array of it's value type but simply suitably aligned raw storage. --- libs/kstd/include/kstd/vector | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 7568cb6..41b380e 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -85,7 +85,7 @@ namespace kstd vector(vector const & other) : _size(other._size) , _capacity(other._capacity) - , _data(new value_type[_capacity]{}) + , _data(new value_type[_capacity]()) { std::ranges::copy(other, _data); } @@ -104,7 +104,7 @@ namespace kstd delete[] _data; _size = other._size; _capacity = other._capacity; - _data = new value_type[_capacity]{}; + _data = new value_type[_capacity](); std::ranges::copy(other, _data); return *this; } @@ -490,7 +490,7 @@ namespace kstd } _capacity = new_capacity; - auto temp = new value_type[_capacity]{}; + auto temp = new value_type[_capacity](); std::ranges::copy(begin(), end(), temp); delete[] _data; _data = temp; -- cgit v1.2.3 From 044a64ef019ce2ff241e72f2b7b3a444b922b798 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 17 Mar 2026 22:31:40 +0100 Subject: kstd: add more nodiscard to shared_ptr --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index ed23d29..6bce83f 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -250,7 +250,7 @@ namespace kstd * * @return Returns the object owned by *this, equivalent to *get(). */ - auto operator*() const -> T & + [[nodiscard]] auto operator*() const -> T & { return *pointer; } @@ -260,7 +260,7 @@ namespace kstd * * @return Returns a pointer to the object owned by *this, i.e. get(). */ - auto operator->() const -> T * + [[nodiscard]] auto operator->() const -> T * { return pointer; } @@ -270,7 +270,7 @@ namespace kstd * * @return Pointer to the managed object or nullptr if no object is owned. */ - auto get() const -> T * + [[nodiscard]] auto get() const -> T * { return pointer; } @@ -301,7 +301,7 @@ namespace kstd * * @return true if *this owns an object, false otherwise. */ - explicit operator bool() const + [[nodiscard]] explicit operator bool() const { return pointer != nullptr; } @@ -317,7 +317,7 @@ namespace kstd /** * @brief Compare nullptr with shared_ptr. */ - friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool + [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool { return ptr.pointer == nullptr; } @@ -325,7 +325,7 @@ namespace kstd /** * @brief Defaulted three-way comparator operator. */ - auto operator<=>(shared_ptr const & other) const = default; + [[nodiscard]] auto operator<=>(shared_ptr const & other) const = default; private: /** -- cgit v1.2.3 From e7ccb96aecae7b231fb05818d7e45a767aebc31d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 18 Mar 2026 17:18:37 +0100 Subject: kstd: introduce strong type for memory amounts --- libs/kstd/include/kstd/units | 145 +++++++++++++++++++++ libs/multiboot2/CMakeLists.txt | 1 + libs/multiboot2/include/multiboot2/information.hpp | 16 ++- .../include/multiboot2/information/data.hpp | 31 ++++- 4 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 libs/kstd/include/kstd/units (limited to 'libs') diff --git a/libs/kstd/include/kstd/units b/libs/kstd/include/kstd/units new file mode 100644 index 0000000..f6dcdb1 --- /dev/null +++ b/libs/kstd/include/kstd/units @@ -0,0 +1,145 @@ +#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; + + 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 \ No newline at end of file diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt index 350a996..b306b66 100644 --- a/libs/multiboot2/CMakeLists.txt +++ b/libs/multiboot2/CMakeLists.txt @@ -20,6 +20,7 @@ target_include_directories("multiboot2" INTERFACE target_link_libraries("multiboot2" INTERFACE "libs::elf" + "libs::kstd" ) set_target_properties("multiboot2" PROPERTIES diff --git a/libs/multiboot2/include/multiboot2/information.hpp b/libs/multiboot2/include/multiboot2/information.hpp index fbd534c..d2fac2e 100644 --- a/libs/multiboot2/include/multiboot2/information.hpp +++ b/libs/multiboot2/include/multiboot2/information.hpp @@ -5,11 +5,12 @@ #include "information/iterator.hpp" // IWYU pragma: export #include "information/tag.hpp" // IWYU pragma: export +#include + #include #include #include -#include #include #include #include @@ -119,7 +120,12 @@ namespace multiboot2 */ [[nodiscard]] auto string() const noexcept -> std::string_view { - return {data(), size()}; + return {data(), vla_tag::size()}; + } + + [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{end_address - start_address}; } }; @@ -130,9 +136,9 @@ namespace multiboot2 using pointer = iterator::pointer; using reference = iterator::reference; - [[nodiscard]] auto size_bytes() const noexcept -> std::size_t + [[nodiscard]] auto size() const noexcept -> kstd::units::bytes { - return m_size; + return kstd::units::bytes{m_size}; } // Range access @@ -190,7 +196,7 @@ namespace multiboot2 { return get>().and_then( [](auto x) -> std::optional> { - if (x.entry_size == elf::section_header_size) + if (x.entry_size_in_B == elf::section_header_size) { return std::optional{x}; } diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/include/multiboot2/information/data.hpp index 9fa6d5b..3b07d20 100644 --- a/libs/multiboot2/include/multiboot2/information/data.hpp +++ b/libs/multiboot2/include/multiboot2/information/data.hpp @@ -6,6 +6,8 @@ #include "multiboot2/constants/information_id.hpp" #include "multiboot2/constants/memory_type.hpp" +#include + #include namespace multiboot2 @@ -27,6 +29,16 @@ namespace multiboot2 //! loader. struct basic_memory : tag_data { + [[nodiscard]] constexpr auto lower() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{lower_KiB * 1024}; + } + + [[nodiscard]] constexpr auto upper() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{upper_KiB * 1024}; + } + //! The amount of lower memory available to the system. //! //! Any memory below the 1 MiB address boundary is considered to be lower memory. The maximum possible value for @@ -72,11 +84,16 @@ namespace multiboot2 //! time. The array begins after the last member of this structure. struct elf_symbols : tag_data { + [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{entry_size_in_B}; + } + //! The number of section header table entries. std::uint32_t count; //! The size of each section header table entry. - std::uint32_t entry_size; + std::uint32_t entry_size_in_B; //! The section number of the string table containing the section names. std::uint32_t string_table_index; @@ -102,6 +119,11 @@ namespace multiboot2 return type == memory_type::available; } + [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{size_in_B}; + } + //! The physical start address of this region std::uint64_t base; @@ -117,11 +139,16 @@ namespace multiboot2 std::uint32_t : 0; }; + [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{entry_size_in_B}; + } + //! The size of each entry present in the map. //! //! This field is present to allow for future extension of the entry format. Each entry's size is guaranteed to //! always be an integer multiple of 8. - std::uint32_t entry_size; + std::uint32_t entry_size_in_B; //! The version of each entry present in the map std::uint32_t entry_version; -- cgit v1.2.3 From c86c898836b1564de2733330540f5d0cd56e8b10 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 18 Mar 2026 17:18:51 +0100 Subject: kstd: don't allocate 0-sized memory regions --- libs/kstd/include/kstd/vector | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 41b380e..6af7c12 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -40,7 +40,7 @@ namespace kstd explicit vector(size_type n, value_type initial = value_type{}) : _size(n) , _capacity(n) - , _data(new value_type[_capacity]{}) + , _data(_capacity ? new value_type[_capacity]{} : nullptr) { std::ranges::fill(*this, initial); } @@ -56,7 +56,7 @@ namespace kstd explicit vector(InputIterator first, InputIterator last) : _size(std::distance(first, last)) , _capacity(std::distance(first, last)) - , _data(new value_type[_capacity]{}) + , _data(_capacity ? new value_type[_capacity]() : nullptr) { std::ranges::copy(first, last, _data); } @@ -69,7 +69,7 @@ namespace kstd explicit vector(std::initializer_list initializer_list) : _size(initializer_list.size()) , _capacity(initializer_list.size()) - , _data(new value_type[_capacity]{}) + , _data(_capacity ? new value_type[_capacity]() : nullptr) { std::ranges::copy(initializer_list, _data); } @@ -85,7 +85,7 @@ namespace kstd vector(vector const & other) : _size(other._size) , _capacity(other._capacity) - , _data(new value_type[_capacity]()) + , _data(_capacity ? new value_type[_capacity]() : nullptr) { std::ranges::copy(other, _data); } @@ -104,7 +104,7 @@ namespace kstd delete[] _data; _size = other._size; _capacity = other._capacity; - _data = new value_type[_capacity](); + _data = _capacity ? new value_type[_capacity]() : nullptr; std::ranges::copy(other, _data); return *this; } -- cgit v1.2.3 From c049d767c7d41d04226f2c6b01ee2e07aae7ea1f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 07:16:58 +0100 Subject: kstd: prepare vector to be allocator aware --- libs/kstd/include/kstd/vector | 426 ++++++++++++++++++++++++++++-------------- 1 file changed, 289 insertions(+), 137 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 6af7c12..b87756a 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -6,6 +6,10 @@ #include #include #include +#include +#include +#include +#include namespace kstd { @@ -14,129 +18,254 @@ namespace kstd * custom memory management. * * @tparam T Element the vector instance should contain. + * @tparam Allocator The allocator to use when allocating new elements. */ - template + template> struct vector { using value_type = T; ///< Type of the elements contained in the container. + using allocator_type = Allocator; ///< Type of the allocator used by the container. using size_type = std::size_t; ///< Type of the size in the container. + using difference_type = std::ptrdiff_t; ///< Type of the difference between two iterators. using reference = value_type &; ///< Type of reference to the elements. using const_reference = value_type const &; ///< Type of constant reference to the elements. using pointer = value_type *; ///< Type of pointer to the elements. using const_pointer = value_type const *; ///< Type of constant pointer to the elements. + using iterator = pointer; ///< Type of iterator to the elements. + using const_iterator = const_pointer; ///< Type of constant iterator to the elements. + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; - /** - * @brief Default Constructor. - */ + //! Construct a new, empty vector. vector() = default; - /** - * @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. - */ + //! Construct a new vector, with the given number of element and initialize them to the given value. + //! + //! @param n The number of elements to construct the container with. + //! @param initial The value to initialize each element to. explicit vector(size_type n, value_type initial = value_type{}) - : _size(n) - , _capacity(n) - , _data(_capacity ? new value_type[_capacity]{} : nullptr) + : m_size{n} + , m_capacity{n} + , m_data{allocate_n(m_capacity)} { - std::ranges::fill(*this, initial); + for (auto i = 0uz; i < n; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, initial); + } } - /** - * @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. - */ + //! 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 explicit vector(InputIterator first, InputIterator last) - : _size(std::distance(first, last)) - , _capacity(std::distance(first, last)) - , _data(_capacity ? new value_type[_capacity]() : nullptr) + : m_allocator{} + , m_size{std::ranges::distance(first, last)} + , m_capacity{std::ranges::distance(first, last)} + , m_data{allocate_n(m_capacity)} { - std::ranges::copy(first, last, _data); + for (auto destination = begin(); first != last; ++first, ++destination) + { + std::allocator_traits::construct(m_allocator, destination, *first); + } } - /** - * @brief Construct data by copying all elements from the initializer list. - * - * @param initializer_list List we want to copy all elements from. - */ - explicit vector(std::initializer_list initializer_list) - : _size(initializer_list.size()) - , _capacity(initializer_list.size()) - , _data(_capacity ? new value_type[_capacity]() : nullptr) - { - std::ranges::copy(initializer_list, _data); + //! 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. + explicit vector(std::initializer_list list) + : vector{std::ranges::begin(list), std::ranges::end(list)} + {} + + //! Construct a new vector and initialize it's content by copying all elements from a given vector. + //! + //! @param other The source vector. + 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)) + { + auto first = std::ranges::begin(other); + auto last = std::ranges::end(other); + for (auto destination = begin(); first != last; ++first, ++destination) + { + std::allocator_traits::construct(m_allocator, destination, *first); + } } - /** - * @brief Copy constructor. - * - * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all - * elements from it. - * - * @param other Other instance of vector we want to copy the data from. - */ - vector(vector const & other) - : _size(other._size) - , _capacity(other._capacity) - , _data(_capacity ? new value_type[_capacity]() : nullptr) + //! Construct a new vector and initialize it's content by moving from a given vector. + //! + //! @param other The source vector. + vector(vector && other) noexcept + : m_allocator{std::exchange(other.m_allocator, allocator_type{})} + , 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)) + {} + + //! Destroy this vector. + ~vector() { - std::ranges::copy(other, _data); + std::ranges::for_each(std::views::reverse(*this), [&](auto & element) { + std::allocator_traits::destroy(m_allocator, std::addressof(element)); + }); + std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); } - /** - * @brief Copy assignment operator. - * - * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all - * elements from it. - * - * @param other Other instance of vector we want to copy the data from. - * @return Newly created copy. - */ + //! Replace the contents of this vector by the copying from the given source vector. + //! + //! @param other The source vector. auto operator=(vector const & other) -> vector & { - delete[] _data; - _size = other._size; - _capacity = other._capacity; - _data = _capacity ? new value_type[_capacity]() : nullptr; - std::ranges::copy(other, _data); + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + if (m_allocator != other.m_allocator) + { + clear_and_deallocate(); + } + m_allocator = other.m_allocator; + } + + if (m_capacity >= other.m_size) + { + auto const overlap = std::min(m_size, other.m_size); + auto first = std::ranges::begin(other); + for (auto i = 0uz; i < overlap; ++first, ++i) + { + m_data[i] = *first; + } + + if (m_size < other.m_size) + { + for (auto i = m_size; i < other.m_size; ++first, ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, *first); + } + } + else if (m_size > other.m_size) + { + for (auto i = other.m_size; i < m_size; ++i) + { + std::allocator_traits::destroy(m_allocator, m_data + i); + } + } + } + else + { + auto new_data = std::allocator_traits::allocate(m_allocator, other.m_size); + for (auto i = 0uz; i < other.m_size; ++i) + { + std::allocator_traits::construct(m_allocator, new_data + i, other.m_data[i]); + } + clear_and_deallocate(); + std::exchange(m_data, new_data); + m_capacity = other.m_size; + } + + m_size = other.m_size; return *this; } - /** - * @brief Destructor. - */ - ~vector() + //! Replace the contents fo this vector by moving from the given source. + //! + //! @param other The source vector. + auto operator=(vector && other) noexcept -> vector & { - delete[] _data; + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + clear_and_deallocate(); + 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); + } + else if (m_allocator != other.m_allocator) + { + clear_and_deallocate(); + 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); + } + else + { + if (m_capacity >= other.m_size) + { + auto const overlap = std::min(m_size, other.m_size); + auto first = std::ranges::begin(other); + for (auto i = 0uz; i < overlap; ++first, ++i) + { + m_data[i] = std::move(*first); + } + + if (m_size < other.m_size) + { + for (auto i = m_size; i < other.m_size; ++first, ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, std::move(*first)); + } + } + else if (m_size > other.m_size) + { + for (auto i = other.m_size; i < m_size; ++i) + { + std::allocator_traits::destroy(m_allocator, m_data + i); + } + } + } + else + { + auto new_data = std::allocator_traits::allocate(m_allocator, other.m_size); + for (auto i = 0uz; i < other.m_size; ++i) + { + std::allocator_traits::destroy(m_allocator, m_data + i); + } + clear_and_deallocate(); + std::exchange(m_data, new_data); + m_capacity = other.m_size; + } + for (auto i = 0uz; i < other.m_size; ++i) + { + std::allocator_traits::destroy(other.m_allocator, other.m_data + i); + } + m_size = std::exchange(other.m_size, size_type{}); + } + + return *this; } /** - * @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. + * @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. */ [[nodiscard]] auto size() const -> size_type { - return _size; + return m_size; } /** - * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than we - * exactly require to decrease the amount of allocations and deallocation to improve speed. + * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than + * we exactly require to decrease the amount of allocations and deallocation to improve speed. * * @return Current amount of space the vector has for elements. */ [[nodiscard]] auto capacity() const -> size_type { - return _capacity; + return m_capacity; } /** @@ -149,7 +278,7 @@ namespace kstd */ auto operator[](size_type index) -> reference { - return _data[index]; + return m_data[index]; } /** @@ -162,13 +291,14 @@ namespace kstd */ auto operator[](size_type index) const -> const_reference { - return _data[index]; + return m_data[index]; } /** * @brief Array indexing operator. Allowing to access element at the given index. * - * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted. + * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is + * halted. * * @param index Index we want to access elements at. * @return Reference to the underlying element. @@ -176,13 +306,14 @@ namespace kstd auto at(size_type index) -> reference { throw_if_out_of_range(index); - return this->operator[](index); + return (*this)[index]; } /** * @brief Array indexing operator. Allowing to access element at the given index. * - * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted. + * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is + * halted. * * @param index Index we want to access elements at. * @return Reference to the underlying element. @@ -190,7 +321,7 @@ namespace kstd [[nodiscard]] auto at(size_type index) const -> const_reference { throw_if_out_of_range(index); - return this->operator[](index); + return (*this)[index]; } /** @@ -209,13 +340,13 @@ namespace kstd auto push_back(U && value) -> void { increase_capacity_if_full(); - _data[_size] = std::forward(value); - (void)_size++; + std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(value)); + ++m_size; } /** - * @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).... + * @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 @@ -230,9 +361,9 @@ namespace kstd auto emplace_back(Args &&... args) -> value_type & { increase_capacity_if_full(); - _data[_size] = value_type{std::forward(args)...}; - auto const index = _size++; - return _data[index]; + std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(args)...); + ++m_size; + return this->back(); } /** @@ -243,7 +374,7 @@ namespace kstd auto pop_back() -> void { throw_if_empty(); - (void)_size--; + (void)m_size--; } /** @@ -254,7 +385,7 @@ namespace kstd */ auto begin() noexcept -> pointer { - return _data; + return m_data; } /** @@ -265,7 +396,7 @@ namespace kstd */ [[nodiscard]] auto begin() const noexcept -> const_pointer { - return _data; + return m_data; } /** @@ -280,30 +411,30 @@ namespace kstd } /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element - * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). + * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last + * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). * * @return Reverse iterator to the first element. */ auto rbegin() noexcept -> pointer { - return _data + _size - 1; + return m_data + m_size - 1; } /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element - * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). + * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last + * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). * * @return Reverse iterator to the first element. */ [[nodiscard]] auto rbegin() const noexcept -> const_pointer { - return _data + _size - 1; + return m_data + m_size - 1; } /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element - * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). + * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last + * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). * * @return Reverse iterator to the first element. */ @@ -320,7 +451,7 @@ namespace kstd */ auto end() noexcept -> pointer { - return _data + _size; + return m_data + m_size; } /** @@ -331,7 +462,7 @@ namespace kstd */ [[nodiscard]] auto end() const noexcept -> const_pointer { - return _data + _size; + return m_data + m_size; } /** @@ -354,7 +485,7 @@ namespace kstd */ auto rend() noexcept -> pointer { - return _data + size() - 1; + return m_data + size() - 1; } /** @@ -366,7 +497,7 @@ namespace kstd */ [[nodiscard]] auto rend() const noexcept -> const_pointer { - return _data + size() - 1; + return m_data + size() - 1; } /** @@ -383,28 +514,28 @@ namespace kstd /** * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range - * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable - * in that case). + * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not + * dereferenceable in that case). * - * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal - * to the address of the first element. + * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares + * equal to the address of the first element. */ auto data() -> pointer { - return _data; + return m_data; } /** * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range - * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable - * in that case). + * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not + * dereferenceable in that case). * - * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal - * to the address of the first element. + * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares + * equal to the address of the first element. */ [[nodiscard]] auto data() const -> const_pointer { - return _data; + return m_data; } /** @@ -469,11 +600,11 @@ namespace kstd * the vector greater than the value of capacity(). * * @note Correctly using reserve() can prevent unnecessary reallocations, but inappropriate uses of reserve() (for - * instance, calling it before every push_back() call) may actually increase the number of reallocations (by causing - * the capacity to grow linearly rather than exponentially) and result in increased computational complexity and - * decreased performance. For example, a function that receives an arbitrary vector by reference and appends - * elements to it should usually not call reserve() on the vector, since it does not know of the vector's usage - * characteristics. + * instance, calling it before every push_back() call) may actually increase the number of reallocations (by + * causing the capacity to grow linearly rather than exponentially) and result in increased computational + * complexity and decreased performance. For example, a function that receives an arbitrary vector by reference + * and appends elements to it should usually not call reserve() on the vector, since it does not know of the + * vector's usage characteristics. * * When inserting a range, the range version of insert() is generally preferable as it preserves the correct * capacity growth behavior, unlike reserve() followed by a series of push_back()s. @@ -484,16 +615,16 @@ namespace kstd */ auto reserve(size_type new_capacity) -> void { - if (new_capacity <= _capacity) + if (new_capacity <= m_capacity) { return; } - _capacity = new_capacity; - auto temp = new value_type[_capacity](); + m_capacity = new_capacity; + auto temp = new value_type[m_capacity](); std::ranges::copy(begin(), end(), temp); - delete[] _data; - _data = temp; + delete[] m_data; + m_data = temp; } /** @@ -504,16 +635,16 @@ namespace kstd */ auto shrink_to_fit() -> void { - if (_size == _capacity) + if (m_size == m_capacity) { return; } - _capacity = _size; - auto temp = new value_type[_capacity]{}; + m_capacity = m_size; + auto temp = new value_type[m_capacity]{}; std::ranges::copy(begin(), end(), temp); - delete[] _data; - _data = temp; + delete[] m_data; + m_data = temp; } /** @@ -523,10 +654,30 @@ namespace kstd */ [[nodiscard]] auto empty() const -> bool { - return _size <= 0; + return m_size <= 0; } private: + auto allocate_n(std::size_t count) -> std::allocator_traits::pointer + { + if (count) + { + return std::allocator_traits::allocate(m_allocator, count); + } + return nullptr; + } + + auto clear_and_deallocate() -> void + { + std::ranges::for_each(std::views::reverse(*this), [&](auto & element) { + std::allocator_traits::destroy(m_allocator, std::addressof(element)); + }); + std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); + m_capacity = 0; + m_size = 0; + m_data = nullptr; + } + /** * @brief Halts the execution of the application if the data container is currently empty. */ @@ -540,7 +691,7 @@ namespace kstd auto throw_if_out_of_range(size_type index) const -> void { - if (index >= _size) + if (index >= m_size) { os::panic("[Vector] Attempted to read element at invalid index"); } @@ -548,20 +699,21 @@ namespace kstd /** * @brief Increases the internal capacity to 1 if it was previously 0 and to * 2 after that, meaning exponential - * growth. This is done to decrease the amount of single allocations done and because a power of 2 in memory size is - * normally perferable for the cache. + * growth. This is done to decrease the amount of single allocations done and because a power of 2 in memory size + * is normally perferable for the cache. */ auto increase_capacity_if_full() -> void { - if (_size == _capacity) + if (m_size == m_capacity) { - reserve(_capacity == 0U ? 1U : _capacity * 2U); + reserve(m_capacity == 0U ? 1U : m_capacity * 2U); } } - size_type _size = {}; ///< Amount of elements in the underlying data container - size_type _capacity = {}; ///< Amount of space for elements in the underlying data container - value_type * _data = {}; ///< Pointer to the first element in the underlying data container + [[no_unique_address]] allocator_type m_allocator{}; + size_type m_size{}; ///< Amount of elements in the underlying data container + size_type m_capacity{}; ///< Amount of space for elements in the underlying data container + value_type * m_data{}; ///< Pointer to the first element in the underlying data container }; } // namespace kstd -- cgit v1.2.3 From cc55324b0f3a988befaecff15e0ff87e3c6a4dee Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 08:08:32 +0100 Subject: kstd: implement default allocator --- libs/kstd/include/kstd/allocator | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 libs/kstd/include/kstd/allocator (limited to 'libs') diff --git a/libs/kstd/include/kstd/allocator b/libs/kstd/include/kstd/allocator new file mode 100644 index 0000000..0de0e10 --- /dev/null +++ b/libs/kstd/include/kstd/allocator @@ -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 -- cgit v1.2.3 From ae2a264b117ecf556f742d8e9c357f906cb3fd83 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 13:00:08 +0100 Subject: kstd: finish preliminary vector implementation --- libs/kstd/include/kstd/ranges | 21 ++ libs/kstd/include/kstd/vector | 810 ++++++++++++++++++++---------------------- 2 files changed, 401 insertions(+), 430 deletions(-) create mode 100644 libs/kstd/include/kstd/ranges (limited to 'libs') diff --git a/libs/kstd/include/kstd/ranges b/libs/kstd/include/kstd/ranges new file mode 100644 index 0000000..78c3adb --- /dev/null +++ b/libs/kstd/include/kstd/ranges @@ -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/include/kstd/vector b/libs/kstd/include/kstd/vector index b87756a..9709c2a 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -1,14 +1,19 @@ #ifndef KSTD_VECTOR_HPP #define KSTD_VECTOR_HPP +#include #include +#include #include +#include +#include #include #include #include #include #include +#include #include namespace kstd @@ -20,7 +25,7 @@ namespace kstd * @tparam T Element the vector instance should contain. * @tparam Allocator The allocator to use when allocating new elements. */ - template> + template> struct vector { using value_type = T; ///< Type of the elements contained in the container. @@ -37,20 +42,51 @@ namespace kstd using const_reverse_iterator = std::reverse_iterator; //! Construct a new, empty vector. - vector() = default; + 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} + {} + + //! 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, with the given number of element and initialize them to the given value. + //! Construct a new vector and fill it with the given number of copy constructed elements. //! - //! @param n The number of elements to construct the container with. - //! @param initial The value to initialize each element to. - explicit vector(size_type n, value_type initial = value_type{}) - : m_size{n} - , m_capacity{n} + //! @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 < n; ++i) + for (auto i = 0uz; i < count; ++i) { - std::allocator_traits::construct(m_allocator, m_data + i, initial); + std::allocator_traits::construct(m_allocator, m_data + i, value); } } @@ -59,66 +95,103 @@ 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. - template - explicit vector(InputIterator first, InputIterator last) - : m_allocator{} + 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{std::ranges::distance(first, last)} - , m_capacity{std::ranges::distance(first, last)} + , m_capacity{m_size} , m_data{allocate_n(m_capacity)} { - for (auto destination = begin(); first != last; ++first, ++destination) + 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 initializer list. + //! Construct a new vector and initialize it's content by copying all elements in the given range. //! - //! @param list The initializer list containing the source objects. - explicit vector(std::initializer_list list) - : vector{std::ranges::begin(list), std::ranges::end(list)} - {} + //! + template + requires(std::ranges::input_range && std::convertible_to, T>) + 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. - vector(vector const & other) + 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)) { - auto first = std::ranges::begin(other); - auto last = std::ranges::end(other); - for (auto destination = begin(); first != last; ++first, ++destination) - { - std::allocator_traits::construct(m_allocator, destination, *first); - } + 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. - vector(vector && other) noexcept - : m_allocator{std::exchange(other.m_allocator, allocator_type{})} + constexpr vector(vector && other) noexcept + : m_allocator{std::move(std::exchange(other.m_allocator, allocator_type{}))} , 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{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. + explicit vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) + : vector{std::ranges::begin(list), std::ranges::end(list), allocator} + {} + //! Destroy this vector. - ~vector() + constexpr ~vector() { - std::ranges::for_each(std::views::reverse(*this), [&](auto & element) { - std::allocator_traits::destroy(m_allocator, std::addressof(element)); - }); - std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); + clear_and_deallocate(); } //! Replace the contents of this vector by the copying from the given source vector. //! //! @param other The source vector. - auto operator=(vector const & other) -> vector & + constexpr auto operator=(vector const & other) -> vector & { if (this == std::addressof(other)) { @@ -127,58 +200,49 @@ namespace kstd if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) { - if (m_allocator != other.m_allocator) + if (get_allocator() != other.get_allocator()) { clear_and_deallocate(); } - m_allocator = other.m_allocator; + m_allocator = other.get_allocator(); } - if (m_capacity >= other.m_size) + if (capacity() >= other.size()) { auto const overlap = std::min(m_size, other.m_size); - auto first = std::ranges::begin(other); - for (auto i = 0uz; i < overlap; ++first, ++i) - { - m_data[i] = *first; - } + copy_elements(other.begin(), begin(), overlap); if (m_size < other.m_size) { - for (auto i = m_size; i < other.m_size; ++first, ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, *first); - } + uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); } else if (m_size > other.m_size) { - for (auto i = other.m_size; i < m_size; ++i) - { - std::allocator_traits::destroy(m_allocator, m_data + i); - } + destroy_n(begin() + other.size(), size() - other.size()); } } else { - auto new_data = std::allocator_traits::allocate(m_allocator, other.m_size); - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits::construct(m_allocator, new_data + i, other.m_data[i]); - } + auto new_data = std::allocator_traits::allocate(m_allocator, other.size()); + uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); clear_and_deallocate(); - std::exchange(m_data, new_data); - m_capacity = other.m_size; + m_data = new_data; + m_capacity = other.size(); } - m_size = other.m_size; + m_size = other.size(); return *this; } //! Replace the contents fo this vector by moving from the given source. //! //! @param other The source vector. - auto operator=(vector && other) noexcept -> 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; @@ -187,478 +251,338 @@ namespace kstd if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) { clear_and_deallocate(); - 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); + 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) + else if (m_allocator == other.m_allocator) { clear_and_deallocate(); - 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); + swap(m_size, other.m_size); + swap(m_capacity, other.m_capacity); + swap(m_data, other.m_data); } else { - if (m_capacity >= other.m_size) + if (capacity() >= other.size()) { - auto const overlap = std::min(m_size, other.m_size); - auto first = std::ranges::begin(other); - for (auto i = 0uz; i < overlap; ++first, ++i) - { - m_data[i] = std::move(*first); - } + auto const overlap = std::min(size(), other.size()); + move_elements(other.begin(), begin(), overlap); - if (m_size < other.m_size) + if (size() < other.size()) { - for (auto i = m_size; i < other.m_size; ++first, ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, std::move(*first)); - } + uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); } - else if (m_size > other.m_size) + else if (size() > other.size()) { - for (auto i = other.m_size; i < m_size; ++i) - { - std::allocator_traits::destroy(m_allocator, m_data + i); - } + destroy_n(begin() + other.size(), size() - other.size()); } } else { - auto new_data = std::allocator_traits::allocate(m_allocator, other.m_size); - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits::destroy(m_allocator, m_data + i); - } + auto new_data = std::allocator_traits::allocate(get_allocator(), other.size()); + uninitialized_move_with_allocator(other.begin(), new_data, other.size()); clear_and_deallocate(); - std::exchange(m_data, new_data); + m_data = new_data; m_capacity = other.m_size; } - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits::destroy(other.m_allocator, other.m_data + i); - } + + other.destroy_n(other.begin(), other.size()); m_size = std::exchange(other.m_size, size_type{}); } return *this; } - /** - * @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. - */ - [[nodiscard]] auto size() const -> size_type + //! 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_size; + return m_allocator; } - /** - * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than - * we exactly require to decrease the amount of allocations and deallocation to improve speed. - * - * @return Current amount of space the vector has for elements. - */ - [[nodiscard]] auto capacity() const -> size_type + //! 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 { - return m_capacity; + panic_if_out_of_bounds(index); + return (*this)[index]; } - /** - * @brief Array indexing operator. Allowing to access element at the given index. - * - * @note Does not do any bounds checks use at() for that. - * - * @param index Index we want to access elements at. - * @return Reference to the underlying element. - */ - auto operator[](size_type index) -> reference - { - return m_data[index]; - } - - /** - * @brief Array indexing operator. Allowing to access element at the given index. - * - * @note Does not do any bounds checks use at() for that. - * - * @param index Index we want to access elements at. - * @return Reference to the underlying element. - */ - auto operator[](size_type index) const -> const_reference - { - return m_data[index]; - } - - /** - * @brief Array indexing operator. Allowing to access element at the given index. - * - * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is - * halted. - * - * @param index Index we want to access elements at. - * @return Reference to the underlying element. - */ - auto at(size_type index) -> reference - { - throw_if_out_of_range(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]; } - /** - * @brief Array indexing operator. Allowing to access element at the given index. - * - * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is - * halted. - * - * @param index Index we want to access elements at. - * @return Reference to the underlying element. - */ - [[nodiscard]] auto at(size_type index) const -> const_reference + //! 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 { - throw_if_out_of_range(index); - return (*this)[index]; + return data()[index]; } - /** - * @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_back(U && value) -> void + //! 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 { - increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(value)); - ++m_size; + return data()[index]; } - /** - * @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_back(Args &&... args) -> value_type & + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() -> reference { - increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(args)...); - ++m_size; - return this->back(); + return *begin(); } - /** - * @brief Removes the last element of the container. 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_back() -> void + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] auto front() const -> const_reference { - throw_if_empty(); - (void)m_size--; + return *begin(); } - /** - * @brief Returns an iterator to the first element of the vector. - * If the vector is empty, the returned iterator will be equal to end(). - * - * @return Iterator to the first element. - */ - auto begin() noexcept -> pointer + //! 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 m_data; + 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(); } - /** - * @brief Returns an iterator to the first element of the vector. - * If the vector is empty, the returned iterator will be equal to end(). - * - * @return Iterator to the first element. - */ - [[nodiscard]] auto begin() const noexcept -> const_pointer + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() noexcept -> pointer { return m_data; } - /** - * @brief Returns an iterator to the first element of the vector. - * If the vector is empty, the returned iterator will be equal to end(). - * - * @return Iterator to the first element. - */ - [[nodiscard]] auto cbegin() const noexcept -> const_pointer + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer { - return begin(); + return m_data; } - /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last - * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). - * - * @return Reverse iterator to the first element. - */ - auto rbegin() noexcept -> pointer + //! 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 m_data + m_size - 1; + return empty() ? end() : data(); } - /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last - * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). - * - * @return Reverse iterator to the first element. - */ - [[nodiscard]] auto rbegin() const noexcept -> const_pointer + //! 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 m_data + m_size - 1; + return empty() ? end() : data(); } - /** - * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last - * element of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend(). - * - * @return Reverse iterator to the first element. - */ - [[nodiscard]] auto crbegin() const noexcept -> const_pointer + //! 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 rbegin(); + return begin(); } - /** - * @brief Returns an iterator to the element following the last element of the vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Iterator to the element following the last element. - */ - auto end() noexcept -> pointer + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() noexcept -> pointer { - return m_data + m_size; + return capacity() ? data() + size() : nullptr; } - /** - * @brief Returns an iterator to the element following the last element of the vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Iterator to the element following the last element. - */ - [[nodiscard]] auto end() const noexcept -> const_pointer + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() const noexcept -> const_pointer { - return m_data + m_size; + return capacity() ? data() + size() : nullptr; } - /** - * @brief Returns an iterator to the element following the last element of the vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Iterator to the element following the last element. - */ - [[nodiscard]] auto cend() const noexcept -> const_pointer + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer { return end(); } - /** - * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It - * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Reverse iterator to the element following the last element. - */ - auto rend() noexcept -> pointer + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator { - return m_data + size() - 1; + return empty() ? rend() : reverse_iterator{begin() + (m_size - 1)}; } - /** - * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It - * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Reverse iterator to the element following the last element. - */ - [[nodiscard]] auto rend() const noexcept -> const_pointer + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator { - return m_data + size() - 1; + return empty() ? rend() : const_reverse_iterator{begin() + (m_size - 1)}; } - /** - * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It - * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a - * placeholder, attempting to access it results in undefined behavior. - * - * @return Reverse iterator to the element following the last element. - */ - [[nodiscard]] auto crend() const noexcept -> const_pointer + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return rbegin(); } - /** - * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range - * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not - * dereferenceable in that case). - * - * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares - * equal to the address of the first element. - */ - auto data() -> pointer + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator { - return m_data; + return reverse_iterator{capacity() ? data() - 1 : nullptr}; } - /** - * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range - * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not - * dereferenceable in that case). - * - * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares - * equal to the address of the first element. - */ - [[nodiscard]] auto data() const -> const_pointer + //! Get a reverse iterator to the reverse end. + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { - return m_data; + return const_reverse_iterator{capacity() ? data() - 1 : nullptr}; } - /** - * @brief Returns a reference to the first element in the container. Calling front on an empty container causes - * undefined behavior. - * - * @return Reference to the first element. - */ - auto front() -> reference + //! Get a reverse iterator to the reverse end. + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { - throw_if_empty(); - return *begin(); + return rend(); } - /** - * @brief Returns a reference to the first element in the container. Calling front on an empty container causes - * undefined behavior. - * - * @return Reference to the first element. - */ - [[nodiscard]] auto front() const -> const_reference + //! Check whether this vector is empty. + [[nodiscard]] constexpr auto empty() const noexcept -> bool { - throw_if_empty(); - return *begin(); + return !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 back() -> reference + //! Get the number of elements present in this vector. + [[nodiscard]] constexpr auto size() const noexcept -> size_type { - throw_if_empty(); - return *rbegin(); + return m_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. - */ - [[nodiscard]] auto back() const -> const_reference + //! Get the maximum possible number of element for this vector. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type { - throw_if_empty(); - return *rbegin(); + return std::allocator_traits::max_size(m_allocator); } - /** - * @brief Increase the capacity of the vector (the total number of elements that the vector can hold without - * requiring reallocation) to a value that's greater or equal to new_cap. If new_cap is greater than the current - * capacity(), new storage is allocated, otherwise the function does nothing. - * - * reserve() does not change the size of the vector. - * - * If new_cap is greater than capacity(), all iterators (including the end() iterator) and all references to the - * elements are invalidated. Otherwise, no iterators or references are invalidated. - * - * After a call to reserve(), insertions will not trigger reallocation unless the insertion would make the size of - * the vector greater than the value of capacity(). - * - * @note Correctly using reserve() can prevent unnecessary reallocations, but inappropriate uses of reserve() (for - * instance, calling it before every push_back() call) may actually increase the number of reallocations (by - * causing the capacity to grow linearly rather than exponentially) and result in increased computational - * complexity and decreased performance. For example, a function that receives an arbitrary vector by reference - * and appends elements to it should usually not call reserve() on the vector, since it does not know of the - * vector's usage characteristics. - * - * When inserting a range, the range version of insert() is generally preferable as it preserves the correct - * capacity growth behavior, unlike reserve() followed by a series of push_back()s. - * - * reserve() cannot be used to reduce the capacity of the container; to that end shrink_to_fit() is provided. - * - * @param new_capacity New capacity of the vector, in number of elements - */ - auto reserve(size_type new_capacity) -> void - { - if (new_capacity <= m_capacity) + //! 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."); return; } + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + for (auto i = 0uz; i < old_size; ++i) + { + std::allocator_traits::construct(m_allocator, new_data + i, std::move((*this)[i])); + } + clear_and_deallocate(); + std::exchange(m_data, new_data); m_capacity = new_capacity; - auto temp = new value_type[m_capacity](); - std::ranges::copy(begin(), end(), temp); - delete[] m_data; - m_data = temp; + m_size = old_size; } - /** - * @brief Requests the removal of unused capacity. Meaning it requests to reduce capacity() to size(). - * - * If reallocation occurs, all iterators (including the end() iterator) and all references to the elements are - * invalidated. If no reallocation occurs, no iterators or references are invalidated. - */ - auto shrink_to_fit() -> void + //! 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; } + auto new_data = allocate_n(m_size); + for (auto & element : *this) + { + std::allocator_traits::construct(m_allocator, new_data++, std::move(element)); + } + clear_and_deallocate(); + std::exchange(m_data, new_data); m_capacity = m_size; - auto temp = new value_type[m_capacity]{}; - std::ranges::copy(begin(), end(), temp); - delete[] m_data; - m_data = temp; } - /** - * @brief Wheter there are currently any items this container or not. - * - * @return True if there are no elements, false if there are. - */ - [[nodiscard]] auto empty() const -> bool + //! Clear the contents of this vector. + constexpr auto clear() noexcept -> void { - return m_size <= 0; + for (auto i = m_size; i > 0; --i) + { + pop_back(); + } + } + + //! Append a given element to this vector via copy construction. + constexpr auto push_back(value_type const & value) -> void + { + increase_capacity_if_full(); + std::allocator_traits::construct(m_allocator, &(*this)[m_size], value); + ++m_size; + } + + //! Append a given element to this vector via move construction. + constexpr auto push_back(value_type && value) -> void + { + increase_capacity_if_full(); + std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::move(value)); + ++m_size; + } + + //! Append a given element to this vector via direct construction. + template + constexpr auto emplace_back(Args &&... args) -> reference + { + increase_capacity_if_full(); + std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(args)...); + ++m_size; + return this->back(); + } + + //! Remove the last element of this vector. + //! + //! If this vector is empty, the behavior is undefined. + auto pop_back() -> void + { + std::allocator_traits::destroy(m_allocator, &(*this)[--m_size]); } private: - auto allocate_n(std::size_t count) -> std::allocator_traits::pointer + [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits::pointer { if (count) { @@ -667,46 +591,72 @@ namespace kstd return nullptr; } - auto clear_and_deallocate() -> void + constexpr auto clear_and_deallocate() -> void + { + clear(); + deallocate(); + } + + constexpr auto copy_elements(const_iterator from, iterator to, size_type count) -> void + { + for (auto i = 0uz; i < count; ++i) + { + *to++ = std::move(*from++); + } + } + + constexpr auto deallocate() { - std::ranges::for_each(std::views::reverse(*this), [&](auto & element) { - std::allocator_traits::destroy(m_allocator, std::addressof(element)); - }); std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); m_capacity = 0; m_size = 0; m_data = nullptr; } - /** - * @brief Halts the execution of the application if the data container is currently empty. - */ - auto throw_if_empty() const -> void + constexpr auto destroy_n(iterator begin, std::size_t count) -> void + { + std::ranges::for_each(begin, begin + count, [&](auto & element) { + std::allocator_traits::destroy(m_allocator, std::addressof(element)); + }); + } + + auto increase_capacity_if_full() -> void + { + if (m_size == m_capacity) + { + reserve(m_capacity == 0U ? 1U : m_capacity * 2U); + } + } + + constexpr auto move_elements(iterator from, iterator to, size_t count) { - if (empty()) + for (auto i = 0uz; i < count; ++i) { - os::panic("[Vector] Attempted to access element of currently empty vector"); + *to++ = std::move(*from++); } } - auto throw_if_out_of_range(size_type index) const -> void + constexpr auto panic_if_out_of_bounds(size_type index) const -> void { if (index >= m_size) { - os::panic("[Vector] Attempted to read element at invalid index"); + os::panic("[kstd:vector] Attempted to read element at invalid index"); } } - /** - * @brief Increases the internal capacity to 1 if it was previously 0 and to * 2 after that, meaning exponential - * growth. This is done to decrease the amount of single allocations done and because a power of 2 in memory size - * is normally perferable for the cache. - */ - auto increase_capacity_if_full() -> void + constexpr auto uninitialized_copy_with_allocator(const_iterator from, iterator to, difference_type count) { - if (m_size == m_capacity) + for (auto i = 0z; i < count; ++i) { - reserve(m_capacity == 0U ? 1U : m_capacity * 2U); + std::allocator_traits::construct(m_allocator, to++, *from++); + } + } + + constexpr auto uninitialized_move_with_allocator(iterator from, iterator to, difference_type count) + { + for (auto i = 0z; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); } } -- cgit v1.2.3 From b038db886fd1e2179f543217f5d3ed7b8d964c4a Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 13:54:37 +0100 Subject: kstd: fix vector bugs --- libs/kstd/include/kstd/vector | 53 ++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 18 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 9709c2a..75f699b 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -95,8 +95,8 @@ 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. - template - constexpr vector(InputIterator first, InputIterator last, + template + constexpr vector(ForwardIterator first, ForwardIterator last, allocator_type const & allocator = allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) : m_allocator{allocator} @@ -114,7 +114,8 @@ namespace kstd //! //! template - requires(std::ranges::input_range && std::convertible_to, T>) + requires(std::ranges::input_range && std::ranges::sized_range && + std::convertible_to, T>) constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{}) : m_allocator{allocator} , m_size{std::ranges::size(range)} @@ -170,10 +171,26 @@ namespace kstd //! @param allocator The allocator to use in the vector. constexpr vector(vector && other, std::type_identity_t const & allocator) : m_allocator{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)) - {} + , 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. //! @@ -198,7 +215,7 @@ namespace kstd return *this; } - if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) { if (get_allocator() != other.get_allocator()) { @@ -529,13 +546,12 @@ namespace kstd } auto new_data = allocate_n(m_size); - for (auto & element : *this) - { - std::allocator_traits::construct(m_allocator, new_data++, std::move(element)); - } + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, old_size); clear_and_deallocate(); std::exchange(m_data, new_data); - m_capacity = m_size; + m_capacity = old_size; + m_size = old_size; } //! Clear the contents of this vector. @@ -551,7 +567,7 @@ namespace kstd constexpr auto push_back(value_type const & value) -> void { increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, &(*this)[m_size], value); + std::allocator_traits::construct(m_allocator, data() + size(), value); ++m_size; } @@ -559,7 +575,7 @@ namespace kstd constexpr auto push_back(value_type && value) -> void { increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::move(value)); + std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); ++m_size; } @@ -568,7 +584,7 @@ namespace kstd constexpr auto emplace_back(Args &&... args) -> reference { increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, &(*this)[m_size], std::forward(args)...); + std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); ++m_size; return this->back(); } @@ -578,7 +594,8 @@ namespace kstd //! If this vector is empty, the behavior is undefined. auto pop_back() -> void { - std::allocator_traits::destroy(m_allocator, &(*this)[--m_size]); + --m_size; + std::allocator_traits::destroy(m_allocator, data() + size()); } private: @@ -601,7 +618,7 @@ namespace kstd { for (auto i = 0uz; i < count; ++i) { - *to++ = std::move(*from++); + *to++ = *from++; } } -- cgit v1.2.3 From 0decd023337694872e2d3a530b4a768049e59f5d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 13:56:03 +0100 Subject: kstd: remove illegal include --- libs/kstd/include/kstd/vector | 1 - 1 file changed, 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 75f699b..8e0377e 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -6,7 +6,6 @@ #include #include -#include #include #include #include -- cgit v1.2.3 From cffc61472430e3c59630201f8f2698e9ce8c0733 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 14:22:12 +0100 Subject: kstd: apply minor cleanup to vector --- libs/kstd/include/kstd/vector | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 8e0377e..a897b47 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -194,7 +194,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. - explicit vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) + vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) : vector{std::ranges::begin(list), std::ranges::end(list), allocator} {} @@ -239,7 +239,7 @@ namespace kstd } else { - auto new_data = std::allocator_traits::allocate(m_allocator, other.size()); + auto new_data = allocate_n(other.size()); uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); clear_and_deallocate(); m_data = new_data; @@ -297,7 +297,7 @@ namespace kstd } else { - auto new_data = std::allocator_traits::allocate(get_allocator(), other.size()); + auto new_data = allocate_n(other.size()); uninitialized_move_with_allocator(other.begin(), new_data, other.size()); clear_and_deallocate(); m_data = new_data; @@ -520,12 +520,9 @@ namespace kstd auto new_data = allocate_n(new_capacity); auto old_size = size(); - for (auto i = 0uz; i < old_size; ++i) - { - std::allocator_traits::construct(m_allocator, new_data + i, std::move((*this)[i])); - } + uninitialized_move_with_allocator(begin(), new_data, size()); clear_and_deallocate(); - std::exchange(m_data, new_data); + m_data = new_data; m_capacity = new_capacity; m_size = old_size; } @@ -660,17 +657,17 @@ namespace kstd } } - constexpr auto uninitialized_copy_with_allocator(const_iterator from, iterator to, difference_type count) + constexpr auto uninitialized_copy_with_allocator(const_iterator from, iterator to, size_type count) { - for (auto i = 0z; i < count; ++i) + for (auto i = 0uz; i < count; ++i) { std::allocator_traits::construct(m_allocator, to++, *from++); } } - constexpr auto uninitialized_move_with_allocator(iterator from, iterator to, difference_type count) + constexpr auto uninitialized_move_with_allocator(iterator from, iterator to, size_type count) { - for (auto i = 0z; i < count; ++i) + for (auto i = 0uz; i < count; ++i) { std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); } -- cgit v1.2.3 From cabcb0a8365e4d9dc8245b618f00d105e757e8d9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 14:42:48 +0100 Subject: kstd: improve vector documentation --- libs/kstd/include/kstd/vector | 103 +++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 27 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index a897b47..de5c059 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -17,27 +17,36 @@ namespace kstd { - /** - * @brief Custom vector implementation mirroring the std::vector to allow for the usage of STL functionality with our - * custom memory management. - * - * @tparam T Element the vector instance should contain. - * @tparam Allocator The allocator to use when allocating new elements. - */ - template> + //! 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 { - using value_type = T; ///< Type of the elements contained in the container. - using allocator_type = Allocator; ///< Type of the allocator used by the container. - using size_type = std::size_t; ///< Type of the size in the container. - using difference_type = std::ptrdiff_t; ///< Type of the difference between two iterators. - using reference = value_type &; ///< Type of reference to the elements. - using const_reference = value_type const &; ///< Type of constant reference to the elements. - using pointer = value_type *; ///< Type of pointer to the elements. - using const_pointer = value_type const *; ///< Type of constant pointer to the elements. - using iterator = pointer; ///< Type of iterator to the elements. - using const_iterator = const_pointer; ///< Type of constant iterator to the elements. + //! 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 = value_type *; + //! The type of pointers to constant elements in this vector. + using const_pointer = value_type const *; + //! 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. @@ -114,7 +123,7 @@ namespace kstd //! template requires(std::ranges::input_range && std::ranges::sized_range && - std::convertible_to, T>) + std::convertible_to, ValueType>) constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{}) : m_allocator{allocator} , m_size{std::ranges::size(range)} @@ -595,6 +604,9 @@ namespace kstd } 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) @@ -604,13 +616,19 @@ namespace kstd return nullptr; } + //! Clear this vector and release it's memory. constexpr auto clear_and_deallocate() -> void { clear(); deallocate(); } - constexpr auto copy_elements(const_iterator from, iterator to, size_type count) -> void + //! Copy a number of elements from one storage location to another. + //! + //! @param from The start of the source range. + //! @param to The start of the target range. + //! @param count The number of element to copy. + constexpr auto static copy_elements(const_iterator from, iterator to, size_type count) -> void { for (auto i = 0uz; i < count; ++i) { @@ -618,6 +636,7 @@ namespace kstd } } + //! Release the memory of this vector. constexpr auto deallocate() { std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); @@ -626,14 +645,19 @@ namespace kstd m_data = nullptr; } - constexpr auto destroy_n(iterator begin, std::size_t count) -> void + //! 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(begin, begin + count, [&](auto & element) { + std::ranges::for_each(first, first + count, [&](auto & element) { std::allocator_traits::destroy(m_allocator, std::addressof(element)); }); } - auto increase_capacity_if_full() -> void + //! Check if there is still room in this vector, and if not allocate more space. + constexpr auto increase_capacity_if_full() -> void { if (m_size == m_capacity) { @@ -641,7 +665,12 @@ namespace kstd } } - constexpr auto move_elements(iterator from, iterator to, size_t count) + //! Move a number of elements from one storage location to another. + //! + //! @param from The start of the source range. + //! @param to The start of the target range. + //! @param count The number of element to copy. + constexpr auto static move_elements(iterator from, iterator to, size_t count) { for (auto i = 0uz; i < count; ++i) { @@ -649,6 +678,9 @@ namespace kstd } } + //! 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) @@ -657,6 +689,11 @@ namespace kstd } } + //! 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 constexpr auto uninitialized_copy_with_allocator(const_iterator from, iterator to, size_type count) { for (auto i = 0uz; i < count; ++i) @@ -665,6 +702,11 @@ namespace kstd } } + //! 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 constexpr auto uninitialized_move_with_allocator(iterator from, iterator to, size_type count) { for (auto i = 0uz; i < count; ++i) @@ -673,10 +715,17 @@ namespace kstd } } + //! The allocator used by this vector. [[no_unique_address]] allocator_type m_allocator{}; - size_type m_size{}; ///< Amount of elements in the underlying data container - size_type m_capacity{}; ///< Amount of space for elements in the underlying data container - value_type * m_data{}; ///< Pointer to the first element in the underlying data container + + //! 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{}; }; } // namespace kstd -- cgit v1.2.3 From c2ba7be0e1e84752d21abdcb4ec4f9df444bc367 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 15:08:18 +0100 Subject: kstd: add vector comparison operators --- libs/kstd/include/kstd/vector | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index de5c059..f8e9ce2 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -728,6 +728,21 @@ namespace kstd 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()); + } + } // namespace kstd #endif -- cgit v1.2.3 From 6c9e50bee8362bd87c66ee10c253d94a78e1459c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 15:08:39 +0100 Subject: kstd/vector: add deduction guides --- libs/kstd/include/kstd/vector | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index f8e9ce2..79530d2 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -743,6 +743,16 @@ namespace kstd 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 a range. + template>> + vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector, Allocator>; + } // namespace kstd #endif -- cgit v1.2.3 From 63395fecd439989734f80e6420e4961557465ff9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 17:05:26 +0100 Subject: kstd/format: enable formatting of bool values --- libs/kstd/include/kstd/bits/formatter.hpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index eadc0ef..a467edd 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -95,7 +95,7 @@ namespace kstd auto prefix = std::array{'0', '\0'}; auto prefix_length = 0uz; - if (specs.alternative_form && value != 0) + if (specs.alternative_form) { switch (base) { @@ -259,6 +259,29 @@ namespace kstd { }; + template<> + struct formatter + { + bits::format_specs specs{}; + + constexpr auto parse(std::string_view context) -> std::string_view + { + return bits::parse_specs(context, specs); + } + + auto format(bool value, format_context & context) const -> void + { + if (specs.type == 's' || specs.type == '\0') + { + context.push(value ? "true" : "false"); + } + else + { + formatter{specs}.format(static_cast(value), context); + } + } + }; + struct format_arg { using formatting_function = std::string_view(void const *, std::string_view, format_context &); -- cgit v1.2.3 From 1865f7a162a496592e236ffcff171e7e7bc47ee2 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 19 Mar 2026 17:21:21 +0100 Subject: kstd/format: add support for formatting of orderings --- libs/kstd/include/kstd/bits/formatter.hpp | 94 +++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index a467edd..7c9c31d 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -5,9 +5,11 @@ #include #include +#include #include #include +#include #include #include #include @@ -282,6 +284,98 @@ namespace kstd } }; + template<> + struct formatter + { + bits::format_specs specs{}; + + constexpr auto parse(std::string_view context) -> std::string_view + { + return bits::parse_specs(context, specs); + } + + auto format(std::strong_ordering value, format_context & context) const -> void + { + if (value == std::strong_ordering::equal) + { + return context.push(specs.alternative_form ? "==" : "equal"); + } + else if (value == std::strong_ordering::equivalent) + { + return context.push(specs.alternative_form ? "==" : "equivalent"); + } + else if (value == std::strong_ordering::greater) + { + return context.push(specs.alternative_form ? ">" : "greater"); + } + else if (value == std::strong_ordering::less) + { + return context.push(specs.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid strong ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format_specs specs{}; + + constexpr auto parse(std::string_view context) -> std::string_view + { + return bits::parse_specs(context, specs); + } + + auto format(std::weak_ordering value, format_context & context) const -> void + { + if (value == std::weak_ordering::equivalent) + { + return context.push(specs.alternative_form ? "==" : "equivalent"); + } + else if (value == std::weak_ordering::greater) + { + return context.push(specs.alternative_form ? ">" : "greater"); + } + else if (value == std::weak_ordering::less) + { + return context.push(specs.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid weak ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format_specs specs{}; + + constexpr auto parse(std::string_view context) -> std::string_view + { + return bits::parse_specs(context, specs); + } + + auto format(std::partial_ordering value, format_context & context) const -> void + { + if (value == std::partial_ordering::equivalent) + { + return context.push(specs.alternative_form ? "==" : "equivalent"); + } + else if (value == std::partial_ordering::greater) + { + return context.push(specs.alternative_form ? ">" : "greater"); + } + else if (value == std::partial_ordering::less) + { + return context.push(specs.alternative_form ? "<" : "less"); + } + else if (value == std::partial_ordering::unordered) + { + return context.push(specs.alternative_form ? "<=>" : "unordered"); + } + kstd::os::panic("[kstd:format] Invalid partial ordering value!"); + } + }; + struct format_arg { using formatting_function = std::string_view(void const *, std::string_view, format_context &); -- cgit v1.2.3 From 4f942e014dab44ccb8850c5921b81d4bd777d831 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 11:19:05 +0100 Subject: kstd: rework formatting to be closer to std --- libs/kstd/include/kstd/bits/format_args.hpp | 62 +++++ libs/kstd/include/kstd/bits/format_context.hpp | 89 ++++++ libs/kstd/include/kstd/bits/format_specifiers.hpp | 204 ++++++++++++++ libs/kstd/include/kstd/bits/format_specs.hpp | 104 ------- libs/kstd/include/kstd/bits/format_string.hpp | 151 +++++----- libs/kstd/include/kstd/bits/formatter.hpp | 325 +++++++++++----------- libs/kstd/include/kstd/bits/print_sink.hpp | 2 - libs/kstd/include/kstd/os/print.hpp | 2 +- libs/kstd/include/kstd/print | 20 +- 9 files changed, 600 insertions(+), 359 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format_args.hpp create mode 100644 libs/kstd/include/kstd/bits/format_specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_specs.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp new file mode 100644 index 0000000..d236a23 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_args.hpp @@ -0,0 +1,62 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include + +#include "kstd/bits/format_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + template + struct formatter; + + struct format_arg + { + using format_function_type = auto(void const * value, format_parse_context & parse_context, + format_context & context) -> void; + + void const * value_pointer; + format_function_type * format_function; + }; + + using format_args = std::span; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast(value_pointer); + auto fmt = formatter>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{std::array{ + format_arg{static_cast(&args), format_trampoline}...}}; + } + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp index b5c7d21..863047d 100644 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -3,11 +3,100 @@ // IWYU pragma: private, include +#include "kstd/os/error.hpp" + +#include #include namespace kstd { + constexpr auto report_format_error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count) + : m_current(format.begin()) + , m_end(format.end()) + , m_argument_count(argument_count) + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + + public: + }; + struct format_context { using writer_function = void(void *, std::string_view); diff --git a/libs/kstd/include/kstd/bits/format_specifiers.hpp b/libs/kstd/include/kstd/bits/format_specifiers.hpp new file mode 100644 index 0000000..8fb50ef --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include "kstd/bits/format_context.hpp" + +#include +#include +#include + +namespace kstd::bits +{ + enum struct alignment : std::uint8_t + { + none, + left, + right, + center, + }; + + enum struct sign_mode : std::uint8_t + { + none, + plus, + minus, + space, + }; + + enum struct width_mode : std::uint8_t + { + none, + static_value, + dynamic_argument_id + }; + + struct format_specifiers + { + char fill{' '}; + alignment align{}; + sign_mode sign{}; + bool alternative_form{}; + bool zero_pad{}; + + width_mode width_mode{}; + std::size_t width_value{}; + char type{}; + }; + + struct format_padding + { + std::size_t left{}; + std::size_t right{}; + }; + + constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers + { + auto specifiers = format_specifiers{}; + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == '}') + { + return specifiers; + } + + if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) + { + specifiers.fill = *it; + switch (*std::next(it)) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 2); + } + else if (*it == '<' || *it == '>' || *it == '^') + { + switch (*it) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 1); + } + + if (it != end && (*it == '+' || *it == '-' || *it == ' ')) + { + switch (*it) + { + case '+': + specifiers.sign = sign_mode::plus; + break; + case '-': + specifiers.sign = sign_mode::minus; + break; + case ' ': + default: + specifiers.sign = sign_mode::space; + break; + } + std::advance(it, 1); + } + + if (it != end && *it == '#') + { + specifiers.alternative_form = true; + std::advance(it, 1); + } + + if (it != end && *it == '0') + { + specifiers.zero_pad = true; + std::advance(it, 1); + } + + if (it != end && *it == '{') + { + specifiers.width_mode = width_mode::dynamic_argument_id; + std::advance(it, 1); + auto argument_id = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + context.check_arg_id(argument_id); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it == end || *it != '}') + { + report_format_error("Expected '}' for dynamic width."); + } + std::advance(it, 1); + specifiers.width_value = argument_id; + } + else if (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_mode = width_mode::static_value; + while (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_value = specifiers.width_value * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + } + + context.advance_to(it); + return specifiers; + } + + constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, + alignment requested_alignment, alignment default_alignment) -> format_padding + { + if (target_width <= content_length) + { + return {}; + } + + auto total_padding = target_width - content_length; + auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; + + switch (effective_alignment) + { + case alignment::center: + { + auto left = total_padding / 2; + auto right = total_padding - left; + return {left, right}; + } + case alignment::left: + return {0, total_padding}; + case alignment::right: + default: + return {total_padding, 0}; + break; + } + } + +} // namespace kstd::bits + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_specs.hpp b/libs/kstd/include/kstd/bits/format_specs.hpp deleted file mode 100644 index 092a875..0000000 --- a/libs/kstd/include/kstd/bits/format_specs.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_SPECS_HPP -#define KSTD_BITS_FORMAT_SPECS_HPP - -// IWYU pragma: private - -#include -#include -#include - -namespace kstd::bits -{ - - struct format_specs - { - std::size_t width{}; - char fill{' '}; - char type{}; - bool align_left{}; - bool sign_plus{}; - bool sign_space{}; - bool alternative_form{}; - bool zero_pad{}; - }; - - constexpr auto parse_specs(std::string_view string, format_specs & specs) -> std::string_view - { - auto current = string.begin(); - auto end = string.end(); - - if (current == end || *current != ':') - { - return {current, end}; - } - - std::advance(current, 1); - - if (current != end && std::next(current) != end && (*std::next(current) == '<' || *std::next(current) == '>')) - { - specs.fill = *current; - specs.align_left = *std::next(current) == '<'; - std::advance(current, 2); - } - else if (current != end) - { - if (*current == '<') - { - specs.align_left = true; - std::advance(current, 1); - } - else if (*current == '>') - { - specs.align_left = false; - std::advance(current, 1); - } - } - - if (current != end) - { - if (*current == '+') - { - specs.sign_plus = true; - std::advance(current, 1); - } - else if (*current == ' ') - { - specs.sign_space = true; - std::advance(current, 1); - } - else if (*current == '-') - { - std::advance(current, 1); - } - } - - if (current != end && *current == '#') - { - specs.alternative_form = true; - std::advance(current, 1); - } - - if (current != end && *current == '0') - { - specs.zero_pad = true; - std::advance(current, 1); - } - - while (current != end && *current >= '0' && *current <= '9') - { - specs.width = specs.width * 10 + (*current - '0'); - std::advance(current, 1); - } - - if (current != end && *current != '}') - { - specs.type = *current; - std::advance(current, 1); - } - - return {current, end}; - } - -} // namespace kstd::bits - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp index 3a15bf0..cbdfb7d 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -3,127 +3,114 @@ // IWYU pragma: private, include -#include +#include "kstd/bits/format_context.hpp" + #include -#include #include +#include namespace kstd { + template + struct formatter; + namespace bits { - auto invalid_format_string(char const *) -> void; - - consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void + template + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void { - auto next_automatic_index = 0uz; - auto placeholder_count = 0uz; - auto has_manual_index = false; - auto has_automatic_index = false; + auto found = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index && !found) + { + using decay_type = std::remove_cvref_t; + auto fmt = formatter{}; + + auto it = fmt.parse(context); + context.advance_to(it); + found = true; + } + ++current_index; + }()); + + if (!found) + { + report_format_error("Argument index out of bounds."); + } + } + } // namespace bits - auto current = string.begin(); - auto end = string.end(); + template + struct format_string + { + template + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + auto context = format_parse_context{str_view, sizeof...(Args)}; + auto it = context.begin(); - while (current != end) + while (it != context.end()) { - if (*current == '{') + if (*it == '{') { - if (std::next(current) != end && *std::next(current) == '{') + ++it; + if (it != context.end() && *it == '{') { - std::advance(current, 2); + ++it; + context.advance_to(it); continue; } - std::advance(current, 1); - - auto index = 0uz; - auto is_manual_index = false; + context.advance_to(it); + auto argument_id = 0uz; - if (current != end && *current >= '0' && *current <= '9') + if (it != context.end() && *it >= '0' && *it <= '9') { - is_manual_index = true; - while (current != end && *current >= '0' && *current <= '9') + while (it != context.end() && *it >= '0' && *it <= '9') { - index = index * 10 + (*current - '0'); - std::advance(current, 1); - } - } - - if (is_manual_index) - { - placeholder_count = std::max(placeholder_count, index + 1); - if (has_automatic_index) - { - invalid_format_string("Cannot mix automatic and manual indexing."); - } - has_manual_index = true; - if (index >= argument_count) - { - invalid_format_string("Argument index out of range"); + argument_id = argument_id * 10 + static_cast(*it - '0'); + ++it; } + context.check_arg_id(argument_id); + context.advance_to(it); } else { - if (has_manual_index) - { - invalid_format_string("Cannot mix automatic and manual indexing."); - } - has_automatic_index = true; - ++placeholder_count; - if (next_automatic_index >= argument_count) - { - invalid_format_string("Not enough arguments provided for format string."); - } - index = next_automatic_index++; + argument_id = context.next_arg_id(); } - while (current != end && *current != '}') + if (it != context.end() && *it == ':') { - std::advance(current, 1); + ++it; + context.advance_to(it); } - if (current == end) + bits::validate_argument(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') { - invalid_format_string("Unexpected end of format string."); + report_format_error("Missing closing '}' in format string."); } - std::advance(current, 1); } - else if (*current == '}') + else if (*it == '}') { - if (std::next(current) != end && *std::next(current) == '}') + ++it; + if (it != context.end() && *it == '}') { - std::advance(current, 2); - continue; + report_format_error("Unescaped '}' in format string."); } - invalid_format_string("Unexpected '}' in format string."); - } - else - { - std::advance(current, 1); } - } - if (argument_count < placeholder_count) - { - invalid_format_string("Not enough arguments provided for format string."); - } - else if (argument_count > placeholder_count) - { - invalid_format_string("Too many arguments provided for format string."); + ++it; + context.advance_to(it); } } - } // namespace bits - - template - struct format_string - { - std::string_view str; - consteval format_string(char const * format) - : str{format} - { - bits::validate_format_string(str, sizeof...(Args)); - } + std::string_view str_view; }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index 7c9c31d..e9a45e0 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -3,8 +3,9 @@ // IWYU pragma: private, include -#include -#include +#include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_specifiers.hpp" + #include #include @@ -21,30 +22,98 @@ namespace kstd { - template + template struct formatter; + template<> + struct formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + + if (it != context.end() && *it == 's') + { + ++it; + } + + if (it != context.end() && *it != '}') + { + report_format_error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + + template<> + struct formatter : formatter + { + auto format(char const * string, format_context & context) const -> void + { + formatter::format(string ? std::string_view{string} : "(null)", context); + } + }; + template struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; - constexpr auto parse(std::string_view context) -> std::string_view + enum struct base { - return bits::parse_specs(context, specs); + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + report_format_error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + report_format_error("Missing terminating '}' in format string."); + } + + return it; } auto format(T value, format_context & context) const -> void { - enum struct base + auto final_width = 0uz; + if (specifiers.width_mode == bits::width_mode::static_value) { - bin = 2, - oct = 8, - dec = 10, - hex = 16, - }; - - constexpr auto static maximum_digits = 80; + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) + { + // TODO: implement argument references so we can read the dynamic width argument. + final_width = 0; + } using unsigned_T = std::make_unsigned_t; auto absolute_value = static_cast(value); @@ -55,11 +124,11 @@ namespace kstd if (value < 0) { is_negative = true; - absolute_value = 0 - value; + absolute_value = 0 - static_cast(value); } } - auto const base = [type = specs.type] -> auto { + auto const base = [type = specifiers.type] -> auto { switch (type) { case 'x': @@ -77,7 +146,7 @@ namespace kstd }(); auto buffer = std::array{}; - auto digits = (specs.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; auto current = buffer.rbegin(); if (absolute_value == 0) @@ -95,21 +164,22 @@ namespace kstd } } + auto content_length = static_cast(std::distance(buffer.rbegin(), current)); auto prefix = std::array{'0', '\0'}; auto prefix_length = 0uz; - if (specs.alternative_form) + if (specifiers.alternative_form) { switch (base) { case base::bin: - prefix[1] = specs.type == 'B' ? 'B' : 'b'; + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; prefix_length = 2; break; case base::oct: prefix_length = 1; break; case base::hex: - prefix[1] = specs.type == 'X' ? 'X' : 'x'; + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; prefix_length = 2; break; default: @@ -122,53 +192,50 @@ namespace kstd { sign_character = '-'; } - else if (specs.sign_plus) + else if (specifiers.sign == bits::sign_mode::plus) { sign_character = '+'; } - else if (specs.sign_space) + else if (specifiers.sign == bits::sign_mode::space) { sign_character = ' '; } - auto const content_length = static_cast(std::distance(buffer.rbegin(), current)); auto const total_length = content_length + prefix_length + (sign_character != '\0'); - auto const padding_length = (specs.width > total_length) ? (specs.width - total_length) : 0; + auto const padding = + bits::calculate_format_padding(final_width, total_length, specifiers.align, bits::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::alignment::none); - if (!specs.align_left && !specs.zero_pad) + if (!effective_zero_pad) { - for (auto i = 0uz; i < padding_length; ++i) + for (auto i = 0uz; i < padding.left; ++i) { - context.push(specs.fill); + context.push(specifiers.fill); } } - if (sign_character) + if (sign_character != '\0') { context.push(sign_character); } - - if (prefix_length) + if (prefix_length > 0) { - context.push({prefix.data(), prefix_length}); + context.push(std::string_view{prefix.data(), prefix_length}); } - if (!specs.align_left && specs.zero_pad) + if (effective_zero_pad) { - for (auto i = 0uz; i < padding_length; ++i) + for (auto i = 0uz; i < padding.left; ++i) { context.push('0'); } } - context.push({current.base(), content_length}); + context.push(std::string_view{current.base(), content_length}); - if (specs.align_left) + for (auto i = 0uz; i < padding.right; ++i) { - for (auto i = 0uz; i < padding_length; ++i) - { - context.push(specs.fill); - } + context.push(specifiers.fill); } } }; @@ -176,13 +243,13 @@ namespace kstd template struct formatter : formatter { - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { auto result = formatter::parse(context); - if (!this->specs.type) + if (!this->specifiers.type) { - this->specs.type = 'p'; - this->specs.alternative_form = true; + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; } return result; } @@ -199,87 +266,64 @@ namespace kstd }; template<> - struct formatter + struct formatter : formatter { - bits::format_specs specs{}; + }; - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); - } + template<> + struct formatter + { + bits::format_specifiers specifiers{}; - auto format(std::string_view string, format_context & context) const -> void + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - auto const content_length = string.size(); - auto const padding_length = (specs.width > content_length) ? (specs.width - content_length) : 0; + specifiers = bits::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); - if (!specs.align_left) + if (it != end && *it != '}') { - for (auto i = 0uz; i < padding_length; ++i) + if (*it == 's') { - context.push(specs.fill); + specifiers.type = *it; + std::advance(it, 1); } - } - - context.push(string); - - if (specs.align_left) - { - for (auto i = 0uz; i < padding_length; ++i) + else { - context.push(specs.fill); + report_format_error("Invalid type specifier for bool."); } } - } - }; - template<> - struct formatter - { - bits::format_specs specs{}; + if (it != end && *it != '}') + { + report_format_error("Missing terminating '}' in format string."); + } - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); + return it; } - auto format(char const * string, format_context & context) const -> void + auto format(bool value, format_context & context) const -> void { - if (string) - { - formatter{specs}.format(string, context); - } - else + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + if (specifiers.width_mode == bits::width_mode::static_value) { - formatter{specs}.format("(null)", context); + final_width = specifiers.width_value; } - } - }; - template<> - struct formatter : formatter - { - }; + auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); - template<> - struct formatter - { - bits::format_specs specs{}; - - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); - } - - auto format(bool value, format_context & context) const -> void - { - if (specs.type == 's' || specs.type == '\0') + for (auto i = 0uz; i < padding.left; ++i) { - context.push(value ? "true" : "false"); + context.push(specifiers.fill); } - else + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) { - formatter{specs}.format(static_cast(value), context); + context.push(specifiers.fill); } } }; @@ -287,30 +331,31 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::strong_ordering value, format_context & context) const -> void { if (value == std::strong_ordering::equal) { - return context.push(specs.alternative_form ? "==" : "equal"); + return context.push(specifiers.alternative_form ? "==" : "equal"); } else if (value == std::strong_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::strong_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::strong_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } kstd::os::panic("[kstd:format] Invalid strong ordering value!"); } @@ -319,26 +364,27 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::weak_ordering value, format_context & context) const -> void { if (value == std::weak_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::weak_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::weak_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } kstd::os::panic("[kstd:format] Invalid weak ordering value!"); } @@ -347,71 +393,36 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::partial_ordering value, format_context & context) const -> void { if (value == std::partial_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::partial_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::partial_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } else if (value == std::partial_ordering::unordered) { - return context.push(specs.alternative_form ? "<=>" : "unordered"); + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); } kstd::os::panic("[kstd:format] Invalid partial ordering value!"); } }; - struct format_arg - { - using formatting_function = std::string_view(void const *, std::string_view, format_context &); - - void const * value; - formatting_function * format; - }; - - struct format_args - { - constexpr format_args(format_arg const * args, std::size_t number_of_args) - : m_args(args) - , m_number_of_args(number_of_args) - {} - - [[nodiscard]] constexpr auto get(std::size_t index) const -> format_arg - { - if (index >= m_number_of_args) - return {.value = nullptr, .format = nullptr}; - return m_args[index]; - } - - private: - format_arg const * m_args; - std::size_t m_number_of_args; - }; - - template - auto format_dispatcher(void const * value, std::string_view format_spec, format_context & context) -> std::string_view - { - auto formatter_for_T = formatter{}; - auto const remainder = formatter_for_T.parse(format_spec); - formatter_for_T.format(*static_cast(value), context); - return remainder; - } - } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/print_sink.hpp b/libs/kstd/include/kstd/bits/print_sink.hpp index 0e0955c..af765e0 100644 --- a/libs/kstd/include/kstd/bits/print_sink.hpp +++ b/libs/kstd/include/kstd/bits/print_sink.hpp @@ -3,8 +3,6 @@ // IWYU pragma: private, include -#include - namespace kstd { diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp index f189042..0dde6e7 100644 --- a/libs/kstd/include/kstd/os/print.hpp +++ b/libs/kstd/include/kstd/os/print.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_OS_PRINT_HPP #define KSTD_OS_PRINT_HPP -#include "kstd/bits/formatter.hpp" +#include "kstd/bits/format_args.hpp" #include "kstd/bits/print_sink.hpp" #include diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print index ffafda9..1ab24bd 100644 --- a/libs/kstd/include/kstd/print +++ b/libs/kstd/include/kstd/print @@ -1,12 +1,12 @@ #ifndef KSTD_PRINT #define KSTD_PRINT +#include "bits/format_args.hpp" #include "bits/print_sink.hpp" // IWYU pragma: export #include "os/print.hpp" #include -#include #include namespace kstd @@ -19,13 +19,10 @@ namespace kstd //! @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 &&... args) -> void + auto print(kstd::format_string...> format, Args const &... args) -> void { - auto arguments = std::array{ - kstd::format_arg{&args, kstd::format_dispatcher>} - ... - }; - os::vprint(print_sink::stdout, format.str, kstd::format_args{arguments.data(), sizeof...(Args)}); + auto const arg_store = kstd::make_format_args(args...); + os::vprint(print_sink::stdout, format.str_view, arg_store.args); } //! @qualifier kernel-defined @@ -37,11 +34,8 @@ namespace kstd // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void { - auto arguments = std::array{ - kstd::format_arg{&args, kstd::format_dispatcher>} - ... - }; - os::vprint(sink, format.str, kstd::format_args{arguments.data(), sizeof...(Args)}); + auto const arg_store = kstd::make_format_args(args...); + os::vprint(sink, format.str_view, arg_store.args); } //! @qualifier kernel-defined @@ -54,7 +48,7 @@ namespace kstd // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) auto println(kstd::format_string...> format, Args &&... args) -> void { - print(format, std::forward(args)...); + print(print_sink::stdout, format, std::forward(args)...); print(print_sink::stdout, "\n"); } -- cgit v1.2.3 From d7147ddd7416a6cce874d290d1615527f4d7cdb9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 12:01:22 +0100 Subject: kstd/format: implement dynamic width support --- libs/kstd/include/kstd/bits/format_args.hpp | 32 +++--- libs/kstd/include/kstd/bits/format_context.hpp | 114 ++++++------------- .../include/kstd/bits/format_parse_context.hpp | 122 +++++++++++++++++++++ libs/kstd/include/kstd/bits/format_specifiers.hpp | 5 +- libs/kstd/include/kstd/bits/format_string.hpp | 30 ++++- libs/kstd/include/kstd/bits/formatter.hpp | 11 +- 6 files changed, 216 insertions(+), 98 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format_parse_context.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp index d236a23..71ee5ae 100644 --- a/libs/kstd/include/kstd/bits/format_args.hpp +++ b/libs/kstd/include/kstd/bits/format_args.hpp @@ -4,28 +4,20 @@ // IWYU pragma: private, include #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include #include -#include #include namespace kstd { + struct format_context; + struct format_parse_context; + template struct formatter; - struct format_arg - { - using format_function_type = auto(void const * value, format_parse_context & parse_context, - format_context & context) -> void; - - void const * value_pointer; - format_function_type * format_function; - }; - - using format_args = std::span; - template struct format_arg_store { @@ -43,6 +35,20 @@ namespace kstd fmt.format(*typed_value_pointer, context); } + template + auto get_size_trampoline(void const * value_pointer) -> std::size_t + { + if constexpr (bits::is_format_width_compatible_v) + { + return static_cast(*static_cast(value_pointer)); + } + else + { + report_format_error("Dynamic width argument is not an integral value."); + return 0; + } + } + template [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store { @@ -53,7 +59,7 @@ namespace kstd else { return format_arg_store{std::array{ - format_arg{static_cast(&args), format_trampoline}...}}; + format_arg{static_cast(&args), format_trampoline, get_size_trampoline}...}}; } } diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp index 863047d..05e37ca 100644 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -3,106 +3,60 @@ // IWYU pragma: private, include -#include "kstd/os/error.hpp" +#include +#include #include +#include #include namespace kstd { - constexpr auto report_format_error(char const * message) -> void + namespace bits { - if consteval - { - extern void compile_time_format_error_triggered(char const *); - compile_time_format_error_triggered(message); - } - else - { - kstd::os::panic("Error while formatting a string."); - } + template + constexpr auto inline is_format_width_compatible_v = std::integral && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as; } - struct format_parse_context - { - using iterator = std::string_view::const_iterator; - - constexpr format_parse_context(std::string_view format, std::size_t argument_count) - : m_current(format.begin()) - , m_end(format.end()) - , m_argument_count(argument_count) - {} - - [[nodiscard]] constexpr auto begin() const -> iterator - { - return m_current; - } - - [[nodiscard]] constexpr auto end() const -> iterator - { - return m_end; - } - - constexpr auto advance_to(iterator position) -> void - { - m_current = position; - } - - constexpr auto next_arg_id() -> std::size_t - { - if (m_mode == index_mode::manual) - { - report_format_error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::automatic; + struct format_parse_context; + struct format_context; - if (m_next_argument_id >= m_argument_count) - { - report_format_error("Argument index out of bounds."); - } - return m_next_argument_id++; - } - - constexpr auto check_arg_id(std::size_t index) -> void - { - if (m_mode == index_mode::automatic) - { - report_format_error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::manual; - - if (index >= m_argument_count) - { - report_format_error("Argument index out of bounds."); - } - } + struct format_arg + { + using format_function_type = auto(void const * value, format_parse_context & parse_context, + format_context & context) -> void; + using get_size_function_type = auto(void const * value) -> std::size_t; - private: - enum class index_mode - { - unknown, - automatic, - manual, - }; - - iterator m_current{}; - iterator m_end{}; - index_mode m_mode{}; - std::size_t m_next_argument_id{}; - std::size_t m_argument_count{}; - - public: + void const * value_pointer; + format_function_type * format_function; + get_size_function_type * get_size_function; }; + using format_args = std::span; + struct format_context { using writer_function = void(void *, std::string_view); writer_function * writer; void * user_data; + format_args args; + + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & + { + if (id >= args.size()) + { + kstd::os::panic("[kstd:format] argument index out of range!"); + } + return args[id]; + } constexpr auto push(std::string_view string) -> void { diff --git a/libs/kstd/include/kstd/bits/format_parse_context.hpp b/libs/kstd/include/kstd/bits/format_parse_context.hpp new file mode 100644 index 0000000..76c3f03 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_parse_context.hpp @@ -0,0 +1,122 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// IWYU pragma: private, include + +#include + +#include +#include + +namespace kstd +{ + + constexpr auto report_format_error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count, + bool const * is_integral = nullptr) + : m_current{format.begin()} + , m_end{format.end()} + , m_argument_count{argument_count} + , m_is_integral{is_integral} + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + } + + constexpr auto check_dynamic_width_id(std::size_t id) -> void + { + check_arg_id(id); + if (m_is_integral && !m_is_integral[id]) + { + report_format_error("Dynamic width argument must be an integral object."); + } + } + + constexpr auto next_dynamic_width_id() -> std::size_t + { + auto const id = next_arg_id(); + if (m_is_integral && !m_is_integral[id]) + { + report_format_error("Dynamic width argument must be an integral object."); + } + return id; + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + bool const * m_is_integral{}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_specifiers.hpp b/libs/kstd/include/kstd/bits/format_specifiers.hpp index 8fb50ef..00cca40 100644 --- a/libs/kstd/include/kstd/bits/format_specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include #include @@ -143,11 +144,11 @@ namespace kstd::bits argument_id = argument_id * 10 + static_cast(*it - '0'); std::advance(it, 1); } - context.check_arg_id(argument_id); + context.check_dynamic_width_id(argument_id); } else { - argument_id = context.next_arg_id(); + argument_id = context.next_dynamic_width_id(); } if (it == end || *it != '}') diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp index cbdfb7d..f16f1ee 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -4,7 +4,9 @@ // IWYU pragma: private, include #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" +#include #include #include #include @@ -41,6 +43,30 @@ namespace kstd report_format_error("Argument index out of bounds."); } } + + template + constexpr auto validate_dynamic_width(std::size_t target_index) -> void + { + auto is_valid_integer = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index) + { + using decay_type = std::remove_cvref_t; + if constexpr (std::is_integral_v) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + report_format_error("Dynamic width argument must be an integral object."); + } + } } // namespace bits template @@ -50,7 +76,9 @@ namespace kstd consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT : str_view{str} { - auto context = format_parse_context{str_view, sizeof...(Args)}; + auto const is_width_compatible = std::array 0 ? sizeof...(Args) : 1)>{ + bits::is_format_width_compatible_v>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; auto it = context.begin(); while (it != context.end()) diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index e9a45e0..e46f6ad 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include "kstd/bits/format_specifiers.hpp" #include @@ -111,8 +112,8 @@ namespace kstd } else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) { - // TODO: implement argument references so we can read the dynamic width argument. - final_width = 0; + auto const & arg = context.arg(specifiers.width_value); + final_width = arg.get_size_function(arg.value_pointer); } using unsigned_T = std::make_unsigned_t; @@ -307,10 +308,16 @@ namespace kstd { auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; auto final_width = 0uz; + if (specifiers.width_mode == bits::width_mode::static_value) { final_width = specifiers.width_value; } + else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = arg.get_size_function(arg.value_pointer); + } auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); -- cgit v1.2.3 From 8365a09e18ac043d2e0aa6a9d3e394a02fe7c6cb Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 12:06:17 +0100 Subject: kstd: fix build system errors --- libs/kstd/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 77b12a9..9b4976b 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -21,10 +21,13 @@ target_sources("kstd" PUBLIC FILE_SET HEADERS BASE_DIRS "include" FILES + "include/kstd/bits/format_args.hpp" "include/kstd/bits/format_context.hpp" - "include/kstd/bits/format_specs.hpp" + "include/kstd/bits/format_parse_context.hpp" + "include/kstd/bits/format_specifiers.hpp" "include/kstd/bits/format_string.hpp" "include/kstd/bits/formatter.hpp" + "include/kstd/bits/print_sink.hpp" "include/kstd/bits/shared_ptr.hpp" "include/kstd/bits/unique_ptr.hpp" -- cgit v1.2.3 From 07cb15c42c16497b0b09b75886ce3baddeaaafb3 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 14:03:45 +0100 Subject: kstd/format: split implementation --- libs/kstd/include/kstd/bits/format/arg.hpp | 26 ++ libs/kstd/include/kstd/bits/format/args.hpp | 75 ++++ libs/kstd/include/kstd/bits/format/context.hpp | 61 +++ libs/kstd/include/kstd/bits/format/error.hpp | 24 ++ libs/kstd/include/kstd/bits/format/formatter.hpp | 14 + .../include/kstd/bits/format/formatter/bool.hpp | 83 ++++ .../include/kstd/bits/format/formatter/cstring.hpp | 29 ++ .../kstd/bits/format/formatter/integral.hpp | 204 ++++++++++ .../kstd/bits/format/formatter/ordering.hpp | 111 ++++++ .../include/kstd/bits/format/formatter/pointer.hpp | 42 ++ .../kstd/bits/format/formatter/string_view.hpp | 41 ++ libs/kstd/include/kstd/bits/format/fwd.hpp | 23 ++ .../include/kstd/bits/format/parse_context.hpp | 109 ++++++ libs/kstd/include/kstd/bits/format/specifiers.hpp | 205 ++++++++++ libs/kstd/include/kstd/bits/format/string.hpp | 146 +++++++ libs/kstd/include/kstd/bits/format_args.hpp | 68 ---- libs/kstd/include/kstd/bits/format_context.hpp | 74 ---- .../include/kstd/bits/format_parse_context.hpp | 122 ------ libs/kstd/include/kstd/bits/format_specifiers.hpp | 205 ---------- libs/kstd/include/kstd/bits/format_string.hpp | 146 ------- libs/kstd/include/kstd/bits/formatter.hpp | 435 --------------------- libs/kstd/include/kstd/format | 15 +- libs/kstd/include/kstd/os/print.hpp | 2 +- libs/kstd/include/kstd/print | 1 - 24 files changed, 1206 insertions(+), 1055 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/arg.hpp create mode 100644 libs/kstd/include/kstd/bits/format/args.hpp create mode 100644 libs/kstd/include/kstd/bits/format/context.hpp create mode 100644 libs/kstd/include/kstd/bits/format/error.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/bool.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/cstring.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/integral.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/ordering.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/pointer.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/string_view.hpp create mode 100644 libs/kstd/include/kstd/bits/format/fwd.hpp create mode 100644 libs/kstd/include/kstd/bits/format/parse_context.hpp create mode 100644 libs/kstd/include/kstd/bits/format/specifiers.hpp create mode 100644 libs/kstd/include/kstd/bits/format/string.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_args.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_parse_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_string.hpp delete mode 100644 libs/kstd/include/kstd/bits/formatter.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp new file mode 100644 index 0000000..92e6431 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/arg.hpp @@ -0,0 +1,26 @@ +#ifndef KSTD_BITS_FORMAT_ARG_HPP +#define KSTD_BITS_FORMAT_ARG_HPP + +// IWYU pragma: private, include + +#include "fwd.hpp" + +#include + +namespace kstd +{ + + struct format_arg + { + using format_function_type = auto(void const * value, format_parse_context & parse_context, + format_context & context) -> void; + using get_size_function_type = auto(void const * value) -> std::size_t; + + void const * value_pointer; + format_function_type * format_function; + get_size_function_type * get_size_function; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp new file mode 100644 index 0000000..5cca3ff --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/args.hpp @@ -0,0 +1,75 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include + +#include "context.hpp" +#include "error.hpp" +#include "fwd.hpp" +#include "parse_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + + template + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast(value_pointer); + auto fmt = formatter>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template + auto get_size_trampoline(void const * value_pointer) -> std::size_t + { + if constexpr (is_width_v) + { + return static_cast(*static_cast(value_pointer)); + } + else + { + error("Dynamic width argument is not an integral value."); + return 0; + } + } + + } // namespace bits::format + + using format_args = std::span; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + using namespace bits::format; + + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{std::array{ + format_arg{static_cast(&args), format_trampoline, get_size_trampoline}...}}; + } + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp new file mode 100644 index 0000000..478a48f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -0,0 +1,61 @@ +#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP +#define KSTD_BITS_FORMAT_CONTEXT_HPP + +// IWYU pragma: private, include + +#include "arg.hpp" + +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto inline is_width_v = std::integral && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as; + } + + struct format_context + { + using writer_function = void(void *, std::string_view); + using format_args = std::span; + + writer_function * writer{}; + void * user_data{}; + format_args args{}; + + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & + { + if (id >= args.size()) + { + kstd::os::panic("[kstd:format] argument index out of range!"); + } + return args[id]; + } + + constexpr auto push(std::string_view string) -> void + { + writer(user_data, string); + } + + constexpr auto push(char character) -> void + { + writer(user_data, std::string_view(&character, 1)); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/error.hpp b/libs/kstd/include/kstd/bits/format/error.hpp new file mode 100644 index 0000000..f0863eb --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/error.hpp @@ -0,0 +1,24 @@ +#ifndef KSTD_BITS_FORMAT_ERROR_HPP +#define KSTD_BITS_FORMAT_ERROR_HPP + +#include "kstd/os/error.hpp" + +namespace kstd::bits::format +{ + + constexpr auto error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp new file mode 100644 index 0000000..bff5f55 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter.hpp @@ -0,0 +1,14 @@ +#ifndef KSTD_BITS_FORMATTER_HPP +#define KSTD_BITS_FORMATTER_HPP + +// IWYU pragma: private, include + +namespace kstd +{ + + template + struct formatter; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp new file mode 100644 index 0000000..bb6cacf --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -0,0 +1,83 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 's') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for bool."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(bool value, format_context & context) const -> void + { + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + + if (specifiers.width_mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = arg.get_size_function(arg.value_pointer); + } + + auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, + bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp new file mode 100644 index 0000000..9afb974 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp @@ -0,0 +1,29 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "string_view.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(char const * string, format_context & context) const -> void + { + formatter::format(string ? std::string_view{string} : "(null)", context); + } + }; + + template<> + struct formatter : formatter + { + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp new file mode 100644 index 0000000..b0caed1 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + bits::format::format_specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; + + enum struct base + { + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(T value, format_context & context) const -> void + { + auto final_width = 0uz; + if (specifiers.width_mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = arg.get_size_function(arg.value_pointer); + } + + using unsigned_T = std::make_unsigned_t; + auto absolute_value = static_cast(value); + auto is_negative = false; + + if constexpr (std::is_signed_v) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - static_cast(value); + } + } + + auto const base = [type = specifiers.type] -> auto { + switch (type) + { + case 'x': + case 'X': + case 'p': + return base::hex; + case 'b': + case 'B': + return base::bin; + case 'o': + return base::oct; + default: + return base::dec; + } + }(); + + auto buffer = std::array{}; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto current = buffer.rbegin(); + + if (absolute_value == 0) + { + *current = '0'; + std::advance(current, 1); + } + else + { + while (absolute_value != 0) + { + *current = digits[absolute_value % std::to_underlying(base)]; + std::advance(current, 1); + absolute_value /= std::to_underlying(base); + } + } + + auto content_length = static_cast(std::distance(buffer.rbegin(), current)); + auto prefix = std::array{'0', '\0'}; + auto prefix_length = 0uz; + if (specifiers.alternative_form) + { + switch (base) + { + case base::bin: + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; + prefix_length = 2; + break; + case base::oct: + prefix_length = 1; + break; + case base::hex: + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; + prefix_length = 2; + break; + default: + break; + } + } + + auto sign_character = '\0'; + if (is_negative) + { + sign_character = '-'; + } + else if (specifiers.sign == bits::format::sign_mode::plus) + { + sign_character = '+'; + } + else if (specifiers.sign == bits::format::sign_mode::space) + { + sign_character = ' '; + } + + auto const total_length = content_length + prefix_length + (sign_character != '\0'); + auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, + bits::format::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); + + if (!effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + } + + if (sign_character != '\0') + { + context.push(sign_character); + } + if (prefix_length > 0) + { + context.push(std::string_view{prefix.data(), prefix_length}); + } + + if (effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push('0'); + } + } + + context.push(std::string_view{current.base(), content_length}); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp new file mode 100644 index 0000000..78e7f7b --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp @@ -0,0 +1,111 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::strong_ordering value, format_context & context) const -> void + { + if (value == std::strong_ordering::equal) + { + return context.push(specifiers.alternative_form ? "==" : "equal"); + } + else if (value == std::strong_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::strong_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::strong_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid strong ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::weak_ordering value, format_context & context) const -> void + { + if (value == std::weak_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::weak_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::weak_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid weak ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::partial_ordering value, format_context & context) const -> void + { + if (value == std::partial_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::partial_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::partial_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + else if (value == std::partial_ordering::unordered) + { + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); + } + kstd::os::panic("[kstd:format] Invalid partial ordering value!"); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp new file mode 100644 index 0000000..fe75a2f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp @@ -0,0 +1,42 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP +#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" +#include "integral.hpp" + +#include +#include + +namespace kstd +{ + + template + struct formatter : formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto result = formatter::parse(context); + if (!this->specifiers.type) + { + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; + } + return result; + } + + auto format(T const * pointer, format_context & context) const -> void + { + formatter::format(std::bit_cast(pointer), context); + } + }; + + template + struct formatter : formatter + { + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp new file mode 100644 index 0000000..f5b698e --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp @@ -0,0 +1,41 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP +#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + + if (it != context.end() && *it == 's') + { + ++it; + } + + if (it != context.end() && *it != '}') + { + bits::format::error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/fwd.hpp b/libs/kstd/include/kstd/bits/format/fwd.hpp new file mode 100644 index 0000000..6caedae --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/fwd.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FWD_HPP +#define KSTD_BITS_FORMAT_FWD_HPP + +// IWYU pragma: private + +#include + +namespace kstd +{ + + struct format_parse_context; + struct format_context; + struct format_arg; + + template + struct formatter; + + template + struct format_arg_store; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/parse_context.hpp b/libs/kstd/include/kstd/bits/format/parse_context.hpp new file mode 100644 index 0000000..063263b --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/parse_context.hpp @@ -0,0 +1,109 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// IWYU pragma: private, include + +#include "error.hpp" + +#include +#include + +namespace kstd +{ + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count, + bool const * is_integral = nullptr) + : m_current{format.begin()} + , m_end{format.end()} + , m_argument_count{argument_count} + , m_is_integral{is_integral} + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + } + + constexpr auto check_dynamic_width_id(std::size_t id) -> void + { + check_arg_id(id); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + } + + constexpr auto next_dynamic_width_id() -> std::size_t + { + auto const id = next_arg_id(); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + return id; + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + bool const * m_is_integral{}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp new file mode 100644 index 0000000..85581e6 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -0,0 +1,205 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include "error.hpp" +#include "parse_context.hpp" + +#include +#include +#include + +namespace kstd::bits::format +{ + enum struct alignment : std::uint8_t + { + none, + left, + right, + center, + }; + + enum struct sign_mode : std::uint8_t + { + none, + plus, + minus, + space, + }; + + enum struct width_mode : std::uint8_t + { + none, + static_value, + dynamic_argument_id + }; + + struct format_specifiers + { + char fill{' '}; + alignment align{}; + sign_mode sign{}; + bool alternative_form{}; + bool zero_pad{}; + + width_mode width_mode{}; + std::size_t width_value{}; + char type{}; + }; + + struct format_padding + { + std::size_t left{}; + std::size_t right{}; + }; + + constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers + { + auto specifiers = format_specifiers{}; + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == '}') + { + return specifiers; + } + + if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) + { + specifiers.fill = *it; + switch (*std::next(it)) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 2); + } + else if (*it == '<' || *it == '>' || *it == '^') + { + switch (*it) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 1); + } + + if (it != end && (*it == '+' || *it == '-' || *it == ' ')) + { + switch (*it) + { + case '+': + specifiers.sign = sign_mode::plus; + break; + case '-': + specifiers.sign = sign_mode::minus; + break; + case ' ': + default: + specifiers.sign = sign_mode::space; + break; + } + std::advance(it, 1); + } + + if (it != end && *it == '#') + { + specifiers.alternative_form = true; + std::advance(it, 1); + } + + if (it != end && *it == '0') + { + specifiers.zero_pad = true; + std::advance(it, 1); + } + + if (it != end && *it == '{') + { + specifiers.width_mode = width_mode::dynamic_argument_id; + std::advance(it, 1); + auto argument_id = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + context.check_dynamic_width_id(argument_id); + } + else + { + argument_id = context.next_dynamic_width_id(); + } + + if (it == end || *it != '}') + { + error("Expected '}' for dynamic width."); + } + std::advance(it, 1); + specifiers.width_value = argument_id; + } + else if (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_mode = width_mode::static_value; + while (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_value = specifiers.width_value * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + } + + context.advance_to(it); + return specifiers; + } + + constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, + alignment requested_alignment, alignment default_alignment) -> format_padding + { + if (target_width <= content_length) + { + return {}; + } + + auto total_padding = target_width - content_length; + auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; + + switch (effective_alignment) + { + case alignment::center: + { + auto left = total_padding / 2; + auto right = total_padding - left; + return {left, right}; + } + case alignment::left: + return {0, total_padding}; + case alignment::right: + default: + return {total_padding, 0}; + break; + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp new file mode 100644 index 0000000..2e7d60a --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -0,0 +1,146 @@ +#ifndef KSTD_BITS_FORMAT_STRING_HPP +#define KSTD_BITS_FORMAT_STRING_HPP + +// IWYU pragma: private, include + +#include "context.hpp" +#include "error.hpp" +#include "parse_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void + { + auto found = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index && !found) + { + using decay_type = std::remove_cvref_t; + auto fmt = formatter{}; + + auto it = fmt.parse(context); + context.advance_to(it); + found = true; + } + ++current_index; + }()); + + if (!found) + { + error("Argument index out of bounds."); + } + } + + template + constexpr auto validate_dynamic_width(std::size_t target_index) -> void + { + auto is_valid_integer = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index) + { + using decay_type = std::remove_cvref_t; + if constexpr (std::is_integral_v) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + error("Dynamic width argument must be an integral object."); + } + } + } // namespace bits::format + + template + struct format_string + { + template + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + using namespace bits::format; + + auto const is_width_compatible = + std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; + auto it = context.begin(); + + while (it != context.end()) + { + if (*it == '{') + { + ++it; + if (it != context.end() && *it == '{') + { + ++it; + context.advance_to(it); + continue; + } + + context.advance_to(it); + auto argument_id = 0uz; + + if (it != context.end() && *it >= '0' && *it <= '9') + { + while (it != context.end() && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + ++it; + } + context.check_arg_id(argument_id); + context.advance_to(it); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it != context.end() && *it == ':') + { + ++it; + context.advance_to(it); + } + + validate_argument(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') + { + bits::format::error("Missing closing '}' in format string."); + } + } + else if (*it == '}') + { + ++it; + if (it != context.end() && *it == '}') + { + bits::format::error("Unescaped '}' in format string."); + } + } + ++it; + context.advance_to(it); + } + } + + std::string_view str_view; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp deleted file mode 100644 index 71ee5ae..0000000 --- a/libs/kstd/include/kstd/bits/format_args.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ARGS_HPP -#define KSTD_BITS_FORMAT_ARGS_HPP - -// IWYU pragma: private, include - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" - -#include -#include -#include - -namespace kstd -{ - struct format_context; - struct format_parse_context; - - template - struct formatter; - - template - struct format_arg_store - { - std::array args{}; - }; - - template - auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) - -> void - { - auto typed_value_pointer = static_cast(value_pointer); - auto fmt = formatter>{}; - auto const it = fmt.parse(parse_context); - parse_context.advance_to(it); - fmt.format(*typed_value_pointer, context); - } - - template - auto get_size_trampoline(void const * value_pointer) -> std::size_t - { - if constexpr (bits::is_format_width_compatible_v) - { - return static_cast(*static_cast(value_pointer)); - } - else - { - report_format_error("Dynamic width argument is not an integral value."); - return 0; - } - } - - template - [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store - { - if constexpr (sizeof...(Arguments) == 0) - { - return format_arg_store<0>{}; - } - else - { - return format_arg_store{std::array{ - format_arg{static_cast(&args), format_trampoline, get_size_trampoline}...}}; - } - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp deleted file mode 100644 index 05e37ca..0000000 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP -#define KSTD_BITS_FORMAT_CONTEXT_HPP - -// IWYU pragma: private, include - -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits - { - template - constexpr auto inline is_format_width_compatible_v = std::integral && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as; - } - - struct format_parse_context; - struct format_context; - - struct format_arg - { - using format_function_type = auto(void const * value, format_parse_context & parse_context, - format_context & context) -> void; - using get_size_function_type = auto(void const * value) -> std::size_t; - - void const * value_pointer; - format_function_type * format_function; - get_size_function_type * get_size_function; - }; - - using format_args = std::span; - - struct format_context - { - using writer_function = void(void *, std::string_view); - - writer_function * writer; - void * user_data; - format_args args; - - [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & - { - if (id >= args.size()) - { - kstd::os::panic("[kstd:format] argument index out of range!"); - } - return args[id]; - } - - constexpr auto push(std::string_view string) -> void - { - writer(user_data, string); - } - - constexpr auto push(char character) -> void - { - writer(user_data, std::string_view(&character, 1)); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_parse_context.hpp b/libs/kstd/include/kstd/bits/format_parse_context.hpp deleted file mode 100644 index 76c3f03..0000000 --- a/libs/kstd/include/kstd/bits/format_parse_context.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP -#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP - -// IWYU pragma: private, include - -#include - -#include -#include - -namespace kstd -{ - - constexpr auto report_format_error(char const * message) -> void - { - if consteval - { - extern void compile_time_format_error_triggered(char const *); - compile_time_format_error_triggered(message); - } - else - { - kstd::os::panic("Error while formatting a string."); - } - } - - struct format_parse_context - { - using iterator = std::string_view::const_iterator; - - constexpr format_parse_context(std::string_view format, std::size_t argument_count, - bool const * is_integral = nullptr) - : m_current{format.begin()} - , m_end{format.end()} - , m_argument_count{argument_count} - , m_is_integral{is_integral} - {} - - [[nodiscard]] constexpr auto begin() const -> iterator - { - return m_current; - } - - [[nodiscard]] constexpr auto end() const -> iterator - { - return m_end; - } - - constexpr auto advance_to(iterator position) -> void - { - m_current = position; - } - - constexpr auto next_arg_id() -> std::size_t - { - if (m_mode == index_mode::manual) - { - report_format_error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::automatic; - - if (m_next_argument_id >= m_argument_count) - { - report_format_error("Argument index out of bounds."); - } - return m_next_argument_id++; - } - - constexpr auto check_arg_id(std::size_t index) -> void - { - if (m_mode == index_mode::automatic) - { - report_format_error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::manual; - - if (index >= m_argument_count) - { - report_format_error("Argument index out of bounds."); - } - } - - constexpr auto check_dynamic_width_id(std::size_t id) -> void - { - check_arg_id(id); - if (m_is_integral && !m_is_integral[id]) - { - report_format_error("Dynamic width argument must be an integral object."); - } - } - - constexpr auto next_dynamic_width_id() -> std::size_t - { - auto const id = next_arg_id(); - if (m_is_integral && !m_is_integral[id]) - { - report_format_error("Dynamic width argument must be an integral object."); - } - return id; - } - - private: - enum class index_mode - { - unknown, - automatic, - manual, - }; - - iterator m_current{}; - iterator m_end{}; - index_mode m_mode{}; - std::size_t m_next_argument_id{}; - std::size_t m_argument_count{}; - bool const * m_is_integral{}; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_specifiers.hpp b/libs/kstd/include/kstd/bits/format_specifiers.hpp deleted file mode 100644 index 00cca40..0000000 --- a/libs/kstd/include/kstd/bits/format_specifiers.hpp +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_SPECS_HPP -#define KSTD_BITS_FORMAT_SPECS_HPP - -// IWYU pragma: private - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" - -#include -#include -#include - -namespace kstd::bits -{ - enum struct alignment : std::uint8_t - { - none, - left, - right, - center, - }; - - enum struct sign_mode : std::uint8_t - { - none, - plus, - minus, - space, - }; - - enum struct width_mode : std::uint8_t - { - none, - static_value, - dynamic_argument_id - }; - - struct format_specifiers - { - char fill{' '}; - alignment align{}; - sign_mode sign{}; - bool alternative_form{}; - bool zero_pad{}; - - width_mode width_mode{}; - std::size_t width_value{}; - char type{}; - }; - - struct format_padding - { - std::size_t left{}; - std::size_t right{}; - }; - - constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers - { - auto specifiers = format_specifiers{}; - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it == '}') - { - return specifiers; - } - - if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) - { - specifiers.fill = *it; - switch (*std::next(it)) - { - case '<': - specifiers.align = alignment::left; - break; - case '>': - specifiers.align = alignment::right; - break; - case '^': - default: - specifiers.align = alignment::center; - break; - } - std::advance(it, 2); - } - else if (*it == '<' || *it == '>' || *it == '^') - { - switch (*it) - { - case '<': - specifiers.align = alignment::left; - break; - case '>': - specifiers.align = alignment::right; - break; - case '^': - default: - specifiers.align = alignment::center; - break; - } - std::advance(it, 1); - } - - if (it != end && (*it == '+' || *it == '-' || *it == ' ')) - { - switch (*it) - { - case '+': - specifiers.sign = sign_mode::plus; - break; - case '-': - specifiers.sign = sign_mode::minus; - break; - case ' ': - default: - specifiers.sign = sign_mode::space; - break; - } - std::advance(it, 1); - } - - if (it != end && *it == '#') - { - specifiers.alternative_form = true; - std::advance(it, 1); - } - - if (it != end && *it == '0') - { - specifiers.zero_pad = true; - std::advance(it, 1); - } - - if (it != end && *it == '{') - { - specifiers.width_mode = width_mode::dynamic_argument_id; - std::advance(it, 1); - auto argument_id = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - context.check_dynamic_width_id(argument_id); - } - else - { - argument_id = context.next_dynamic_width_id(); - } - - if (it == end || *it != '}') - { - report_format_error("Expected '}' for dynamic width."); - } - std::advance(it, 1); - specifiers.width_value = argument_id; - } - else if (it != end && *it >= '0' && *it <= '9') - { - specifiers.width_mode = width_mode::static_value; - while (it != end && *it >= '0' && *it <= '9') - { - specifiers.width_value = specifiers.width_value * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - } - - context.advance_to(it); - return specifiers; - } - - constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, - alignment requested_alignment, alignment default_alignment) -> format_padding - { - if (target_width <= content_length) - { - return {}; - } - - auto total_padding = target_width - content_length; - auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; - - switch (effective_alignment) - { - case alignment::center: - { - auto left = total_padding / 2; - auto right = total_padding - left; - return {left, right}; - } - case alignment::left: - return {0, total_padding}; - case alignment::right: - default: - return {total_padding, 0}; - break; - } - } - -} // namespace kstd::bits - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp deleted file mode 100644 index f16f1ee..0000000 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_STRING_HPP -#define KSTD_BITS_FORMAT_STRING_HPP - -// IWYU pragma: private, include - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" - -#include -#include -#include -#include - -namespace kstd -{ - - template - struct formatter; - - namespace bits - { - template - constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void - { - auto found = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index && !found) - { - using decay_type = std::remove_cvref_t; - auto fmt = formatter{}; - - auto it = fmt.parse(context); - context.advance_to(it); - found = true; - } - ++current_index; - }()); - - if (!found) - { - report_format_error("Argument index out of bounds."); - } - } - - template - constexpr auto validate_dynamic_width(std::size_t target_index) -> void - { - auto is_valid_integer = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index) - { - using decay_type = std::remove_cvref_t; - if constexpr (std::is_integral_v) - { - is_valid_integer = true; - } - } - ++current_index; - }()); - - if (!is_valid_integer) - { - report_format_error("Dynamic width argument must be an integral object."); - } - } - } // namespace bits - - template - struct format_string - { - template - consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT - : str_view{str} - { - auto const is_width_compatible = std::array 0 ? sizeof...(Args) : 1)>{ - bits::is_format_width_compatible_v>...}; - auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; - auto it = context.begin(); - - while (it != context.end()) - { - if (*it == '{') - { - ++it; - if (it != context.end() && *it == '{') - { - ++it; - context.advance_to(it); - continue; - } - - context.advance_to(it); - auto argument_id = 0uz; - - if (it != context.end() && *it >= '0' && *it <= '9') - { - while (it != context.end() && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - ++it; - } - context.check_arg_id(argument_id); - context.advance_to(it); - } - else - { - argument_id = context.next_arg_id(); - } - - if (it != context.end() && *it == ':') - { - ++it; - context.advance_to(it); - } - - bits::validate_argument(argument_id, context); - - it = context.begin(); - if (it == context.end() || *it != '}') - { - report_format_error("Missing closing '}' in format string."); - } - } - else if (*it == '}') - { - ++it; - if (it != context.end() && *it == '}') - { - report_format_error("Unescaped '}' in format string."); - } - } - ++it; - context.advance_to(it); - } - } - - std::string_view str_view; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp deleted file mode 100644 index e46f6ad..0000000 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ /dev/null @@ -1,435 +0,0 @@ -#ifndef KSTD_BITS_FORMATTER_HPP -#define KSTD_BITS_FORMATTER_HPP - -// IWYU pragma: private, include - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" -#include "kstd/bits/format_specifiers.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct formatter; - - template<> - struct formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto it = context.begin(); - - if (it != context.end() && *it == 's') - { - ++it; - } - - if (it != context.end() && *it != '}') - { - report_format_error("Invalid specifier for string_view."); - } - return it; - } - - auto format(std::string_view const & string, format_context & context) const -> void - { - context.push(string); - } - }; - - template<> - struct formatter : formatter - { - auto format(char const * string, format_context & context) const -> void - { - formatter::format(string ? std::string_view{string} : "(null)", context); - } - }; - - template - struct formatter - { - bits::format_specifiers specifiers{}; - - constexpr auto static maximum_digits = 80; - - enum struct base - { - bin = 2, - oct = 8, - dec = 10, - hex = 16, - }; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - report_format_error("Invalid type specifier for integral type."); - } - } - - if (it != end && *it != '}') - { - report_format_error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(T value, format_context & context) const -> void - { - auto final_width = 0uz; - if (specifiers.width_mode == bits::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = arg.get_size_function(arg.value_pointer); - } - - using unsigned_T = std::make_unsigned_t; - auto absolute_value = static_cast(value); - auto is_negative = false; - - if constexpr (std::is_signed_v) - { - if (value < 0) - { - is_negative = true; - absolute_value = 0 - static_cast(value); - } - } - - auto const base = [type = specifiers.type] -> auto { - switch (type) - { - case 'x': - case 'X': - case 'p': - return base::hex; - case 'b': - case 'B': - return base::bin; - case 'o': - return base::oct; - default: - return base::dec; - } - }(); - - auto buffer = std::array{}; - auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; - auto current = buffer.rbegin(); - - if (absolute_value == 0) - { - *current = '0'; - std::advance(current, 1); - } - else - { - while (absolute_value != 0) - { - *current = digits[absolute_value % std::to_underlying(base)]; - std::advance(current, 1); - absolute_value /= std::to_underlying(base); - } - } - - auto content_length = static_cast(std::distance(buffer.rbegin(), current)); - auto prefix = std::array{'0', '\0'}; - auto prefix_length = 0uz; - if (specifiers.alternative_form) - { - switch (base) - { - case base::bin: - prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; - prefix_length = 2; - break; - case base::oct: - prefix_length = 1; - break; - case base::hex: - prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; - prefix_length = 2; - break; - default: - break; - } - } - - auto sign_character = '\0'; - if (is_negative) - { - sign_character = '-'; - } - else if (specifiers.sign == bits::sign_mode::plus) - { - sign_character = '+'; - } - else if (specifiers.sign == bits::sign_mode::space) - { - sign_character = ' '; - } - - auto const total_length = content_length + prefix_length + (sign_character != '\0'); - auto const padding = - bits::calculate_format_padding(final_width, total_length, specifiers.align, bits::alignment::right); - auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::alignment::none); - - if (!effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - } - - if (sign_character != '\0') - { - context.push(sign_character); - } - if (prefix_length > 0) - { - context.push(std::string_view{prefix.data(), prefix_length}); - } - - if (effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push('0'); - } - } - - context.push(std::string_view{current.base(), content_length}); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - - template - struct formatter : formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto result = formatter::parse(context); - if (!this->specifiers.type) - { - this->specifiers.type = 'p'; - this->specifiers.alternative_form = true; - } - return result; - } - - auto format(T const * pointer, format_context & context) const -> void - { - formatter::format(std::bit_cast(pointer), context); - } - }; - - template - struct formatter : formatter - { - }; - - template<> - struct formatter : formatter - { - }; - - template<> - struct formatter - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 's') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - report_format_error("Invalid type specifier for bool."); - } - } - - if (it != end && *it != '}') - { - report_format_error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(bool value, format_context & context) const -> void - { - auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; - auto final_width = 0uz; - - if (specifiers.width_mode == bits::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = arg.get_size_function(arg.value_pointer); - } - - auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(text); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - - template<> - struct formatter - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::strong_ordering value, format_context & context) const -> void - { - if (value == std::strong_ordering::equal) - { - return context.push(specifiers.alternative_form ? "==" : "equal"); - } - else if (value == std::strong_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::strong_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::strong_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid strong ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::weak_ordering value, format_context & context) const -> void - { - if (value == std::weak_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::weak_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::weak_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid weak ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::partial_ordering value, format_context & context) const -> void - { - if (value == std::partial_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::partial_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::partial_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - else if (value == std::partial_ordering::unordered) - { - return context.push(specifiers.alternative_form ? "<=>" : "unordered"); - } - kstd::os::panic("[kstd:format] Invalid partial ordering value!"); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index 0a1bcd1..76aaed8 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -1,8 +1,17 @@ #ifndef KSTD_FORMAT_HPP #define KSTD_FORMAT_HPP -#include "bits/format_context.hpp" // IWYU pragma: export -#include "bits/format_string.hpp" // IWYU pragma: export -#include "bits/formatter.hpp" // IWYU pragma: export +#include "bits/format/arg.hpp" // IWYU pragma: export +#include "bits/format/args.hpp" // IWYU pragma: export +#include "bits/format/context.hpp" // IWYU pragma: export +#include "bits/format/formatter.hpp" // IWYU pragma: export +#include "bits/format/formatter/bool.hpp" // IWYU pragma: export +#include "bits/format/formatter/cstring.hpp" // IWYU pragma: export +#include "bits/format/formatter/integral.hpp" // IWYU pragma: export +#include "bits/format/formatter/ordering.hpp" // IWYU pragma: export +#include "bits/format/formatter/pointer.hpp" // IWYU pragma: export +#include "bits/format/formatter/string_view.hpp" // IWYU pragma: export +#include "bits/format/parse_context.hpp" // IWYU pragma: export +#include "bits/format/string.hpp" // IWYU pragma: export #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp index 0dde6e7..b8e0732 100644 --- a/libs/kstd/include/kstd/os/print.hpp +++ b/libs/kstd/include/kstd/os/print.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_OS_PRINT_HPP #define KSTD_OS_PRINT_HPP -#include "kstd/bits/format_args.hpp" +#include "kstd/bits/format/args.hpp" #include "kstd/bits/print_sink.hpp" #include diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print index 1ab24bd..f91cb04 100644 --- a/libs/kstd/include/kstd/print +++ b/libs/kstd/include/kstd/print @@ -1,7 +1,6 @@ #ifndef KSTD_PRINT #define KSTD_PRINT -#include "bits/format_args.hpp" #include "bits/print_sink.hpp" // IWYU pragma: export #include "os/print.hpp" -- cgit v1.2.3 From 5f7d5c6dafd97b0fe59fdaadc3d87dc02f69c218 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 14:11:07 +0100 Subject: kstd/fmt: clean up naming --- .../include/kstd/bits/format/formatter/bool.hpp | 2 +- .../kstd/bits/format/formatter/integral.hpp | 2 +- .../kstd/bits/format/formatter/ordering.hpp | 6 +-- libs/kstd/include/kstd/bits/format/specifiers.hpp | 46 +++++++++++----------- libs/kstd/include/kstd/bits/format/string.hpp | 1 + 5 files changed, 29 insertions(+), 28 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index bb6cacf..b409e06 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -16,7 +16,7 @@ namespace kstd template<> struct formatter { - bits::format::format_specifiers specifiers{}; + bits::format::specifiers specifiers{}; constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index b0caed1..d5cd6c5 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -21,7 +21,7 @@ namespace kstd template struct formatter { - bits::format::format_specifiers specifiers{}; + bits::format::specifiers specifiers{}; constexpr auto static maximum_digits = 80; diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp index 78e7f7b..758285d 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp @@ -14,7 +14,7 @@ namespace kstd template<> struct formatter { - bits::format::format_specifiers specifiers{}; + bits::format::specifiers specifiers{}; constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { @@ -47,7 +47,7 @@ namespace kstd template<> struct formatter { - bits::format::format_specifiers specifiers{}; + bits::format::specifiers specifiers{}; constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { @@ -76,7 +76,7 @@ namespace kstd template<> struct formatter { - bits::format::format_specifiers specifiers{}; + bits::format::specifiers specifiers{}; constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 85581e6..9bc66c7 100644 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -35,7 +35,7 @@ namespace kstd::bits::format dynamic_argument_id }; - struct format_specifiers + struct specifiers { char fill{' '}; alignment align{}; @@ -48,37 +48,37 @@ namespace kstd::bits::format char type{}; }; - struct format_padding + struct padding { std::size_t left{}; std::size_t right{}; }; - constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers + constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers { - auto specifiers = format_specifiers{}; + auto specs = specifiers{}; auto it = context.begin(); auto const end = context.end(); if (it != end && *it == '}') { - return specifiers; + return specs; } if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) { - specifiers.fill = *it; + specs.fill = *it; switch (*std::next(it)) { case '<': - specifiers.align = alignment::left; + specs.align = alignment::left; break; case '>': - specifiers.align = alignment::right; + specs.align = alignment::right; break; case '^': default: - specifiers.align = alignment::center; + specs.align = alignment::center; break; } std::advance(it, 2); @@ -88,14 +88,14 @@ namespace kstd::bits::format switch (*it) { case '<': - specifiers.align = alignment::left; + specs.align = alignment::left; break; case '>': - specifiers.align = alignment::right; + specs.align = alignment::right; break; case '^': default: - specifiers.align = alignment::center; + specs.align = alignment::center; break; } std::advance(it, 1); @@ -106,14 +106,14 @@ namespace kstd::bits::format switch (*it) { case '+': - specifiers.sign = sign_mode::plus; + specs.sign = sign_mode::plus; break; case '-': - specifiers.sign = sign_mode::minus; + specs.sign = sign_mode::minus; break; case ' ': default: - specifiers.sign = sign_mode::space; + specs.sign = sign_mode::space; break; } std::advance(it, 1); @@ -121,19 +121,19 @@ namespace kstd::bits::format if (it != end && *it == '#') { - specifiers.alternative_form = true; + specs.alternative_form = true; std::advance(it, 1); } if (it != end && *it == '0') { - specifiers.zero_pad = true; + specs.zero_pad = true; std::advance(it, 1); } if (it != end && *it == '{') { - specifiers.width_mode = width_mode::dynamic_argument_id; + specs.width_mode = width_mode::dynamic_argument_id; std::advance(it, 1); auto argument_id = 0uz; @@ -156,24 +156,24 @@ namespace kstd::bits::format error("Expected '}' for dynamic width."); } std::advance(it, 1); - specifiers.width_value = argument_id; + specs.width_value = argument_id; } else if (it != end && *it >= '0' && *it <= '9') { - specifiers.width_mode = width_mode::static_value; + specs.width_mode = width_mode::static_value; while (it != end && *it >= '0' && *it <= '9') { - specifiers.width_value = specifiers.width_value * 10 + static_cast(*it - '0'); + specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); std::advance(it, 1); } } context.advance_to(it); - return specifiers; + return specs; } constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, - alignment requested_alignment, alignment default_alignment) -> format_padding + alignment requested_alignment, alignment default_alignment) -> padding { if (target_width <= content_length) { diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp index 2e7d60a..edeaed1 100644 --- a/libs/kstd/include/kstd/bits/format/string.hpp +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -5,6 +5,7 @@ #include "context.hpp" #include "error.hpp" +#include "formatter.hpp" #include "parse_context.hpp" #include -- cgit v1.2.3 From e92343922bc8dce0c5653b83789498d4c97ade62 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 14:13:43 +0100 Subject: kstd: simplify header packaging --- libs/kstd/CMakeLists.txt | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 9b4976b..877a04e 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -17,32 +17,13 @@ target_sources("kstd" PRIVATE "src/mutex.cpp" ) +file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") + target_sources("kstd" PUBLIC FILE_SET HEADERS BASE_DIRS "include" FILES - "include/kstd/bits/format_args.hpp" - "include/kstd/bits/format_context.hpp" - "include/kstd/bits/format_parse_context.hpp" - "include/kstd/bits/format_specifiers.hpp" - "include/kstd/bits/format_string.hpp" - "include/kstd/bits/formatter.hpp" - "include/kstd/bits/print_sink.hpp" - "include/kstd/bits/shared_ptr.hpp" - "include/kstd/bits/unique_ptr.hpp" - - "include/kstd/ext/bitfield_enum" - - "include/kstd/os/error.hpp" - "include/kstd/os/print.hpp" - - "include/kstd/asm_ptr" - "include/kstd/cstring" - "include/kstd/format" - "include/kstd/memory" - "include/kstd/mutex" - "include/kstd/stack" - "include/kstd/vector" + ${KSTD_HEADERS} ) target_include_directories("kstd" PUBLIC -- cgit v1.2.3 From 8ae0f5a9a83aa58f2bd9eacfb51369b0bf966809 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 15:22:37 +0100 Subject: kstd/format: use tagged union to reduce template bloat --- libs/kstd/include/kstd/bits/format/arg.hpp | 63 +++++++++++-- libs/kstd/include/kstd/bits/format/args.hpp | 100 +++++++++++++++++++-- .../include/kstd/bits/format/formatter/bool.hpp | 2 +- .../include/kstd/bits/format/formatter/cstring.hpp | 6 ++ .../kstd/bits/format/formatter/integral.hpp | 2 +- 5 files changed, 156 insertions(+), 17 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp index 92e6431..a9a6ab5 100644 --- a/libs/kstd/include/kstd/bits/format/arg.hpp +++ b/libs/kstd/include/kstd/bits/format/arg.hpp @@ -3,24 +3,73 @@ // IWYU pragma: private, include +#include "error.hpp" #include "fwd.hpp" #include +#include +#include namespace kstd { - struct format_arg + namespace bits::format { - using format_function_type = auto(void const * value, format_parse_context & parse_context, - format_context & context) -> void; - using get_size_function_type = auto(void const * value) -> std::size_t; + enum struct arg_type : std::uint8_t + { + none, + boolean, + character, + integer, + unsigned_integer, + string_view, + c_string, + pointer, + user_defined, + }; + } // namespace bits::format - void const * value_pointer; - format_function_type * format_function; - get_size_function_type * get_size_function; + struct format_arg + { + bits::format::arg_type type{}; + union + { + bool boolean; + char character; + std::int64_t integer; + std::uint64_t unsigned_integer; + std::string_view string_view; + char const * c_string; + void const * pointer; + struct + { + void const * pointer; + auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void; + } user_defined; + } value{}; }; + namespace bits::format + { + constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t + { + if (arg.type == arg_type::unsigned_integer) + { + return static_cast(arg.value.unsigned_integer); + } + else if (arg.type == arg_type::integer) + { + if (arg.value.integer < 0) + { + error("Dynamic width cannont be negative."); + } + return static_cast(arg.value.integer); + } + error("Dynamic width argument is not an integral value."); + return 0; + } + } // namespace bits::format + } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp index 5cca3ff..008cc03 100644 --- a/libs/kstd/include/kstd/bits/format/args.hpp +++ b/libs/kstd/include/kstd/bits/format/args.hpp @@ -3,14 +3,17 @@ // IWYU pragma: private, include +#include "arg.hpp" #include "context.hpp" -#include "error.hpp" #include "fwd.hpp" #include "parse_context.hpp" #include +#include #include +#include #include +#include #include namespace kstd @@ -31,17 +34,98 @@ namespace kstd } template - auto get_size_trampoline(void const * value_pointer) -> std::size_t + constexpr auto determine_arg_type() -> arg_type { - if constexpr (is_width_v) + using decay_type = std::remove_cvref_t>; + if constexpr (std::same_as) { - return static_cast(*static_cast(value_pointer)); + return arg_type::boolean; + } + else if constexpr (std::same_as) + { + return arg_type::character; + } + else if constexpr (std::integral && std::is_signed_v) + { + return arg_type::integer; + } + else if constexpr (std::integral && std::is_unsigned_v) + { + return arg_type::unsigned_integer; + } + else if constexpr (std::same_as) + { + return arg_type::string_view; + } + else if constexpr (std::same_as || std::same_as) + { + return arg_type::c_string; + } + else if constexpr (std::is_pointer_v || std::same_as) + { + if constexpr (std::same_as || std::same_as) + { + return arg_type::user_defined; + } + else + { + return arg_type::pointer; + } + } + else + { + return arg_type::user_defined; + } + } + + template + constexpr auto make_single_arg(ValueType const & value) -> format_arg + { + auto result = format_arg{}; + constexpr auto type = determine_arg_type(); + result.type = type; + + if constexpr (type == arg_type::boolean) + { + result.value.boolean = value; + } + else if constexpr (type == arg_type::character) + { + result.value.character = value; + } + else if constexpr (type == arg_type::integer) + { + result.value.integer = static_cast(value); + } + else if constexpr (type == arg_type::unsigned_integer) + { + result.value.unsigned_integer = static_cast(value); + } + else if constexpr (type == arg_type::string_view) + { + result.value.string_view = value; + } + else if constexpr (type == arg_type::c_string) + { + result.value.c_string = value; + } + else if constexpr (type == arg_type::pointer) + { + if constexpr (std::same_as, std::nullptr_t>) + { + result.value.pointer = nullptr; + } + else + { + result.value.pointer = static_cast(value); + } } else { - error("Dynamic width argument is not an integral value."); - return 0; + result.value.user_defined.pointer = &value; + result.value.user_defined.format = format_trampoline; } + return result; } } // namespace bits::format @@ -65,8 +149,8 @@ namespace kstd } else { - return format_arg_store{std::array{ - format_arg{static_cast(&args), format_trampoline, get_size_trampoline}...}}; + return format_arg_store{ + std::array{make_single_arg(args)...}}; } } diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index b409e06..336e1b0 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -58,7 +58,7 @@ namespace kstd else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); - final_width = arg.get_size_function(arg.value_pointer); + final_width = bits::format::extrat_dynamic_width(arg); } auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp index 9afb974..bf52f2e 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp @@ -5,6 +5,7 @@ #include "../formatter.hpp" #include "string_view.hpp" +#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/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index d5cd6c5..4912a44 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -71,7 +71,7 @@ namespace kstd else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); - final_width = arg.get_size_function(arg.value_pointer); + final_width = bits::format::extrat_dynamic_width(arg); } using unsigned_T = std::make_unsigned_t; -- cgit v1.2.3 From aa2b6bc208785c1d8ced8451478b14433c762896 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:00:14 +0100 Subject: kstd/format: fix type decay for c strings --- libs/kstd/include/kstd/bits/format/args.hpp | 5 +++-- libs/kstd/include/kstd/bits/format/formatter.hpp | 7 ++++++- libs/kstd/include/kstd/bits/format/formatter/cstring.hpp | 6 ------ libs/kstd/include/kstd/bits/format/string.hpp | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp index 008cc03..d1586ac 100644 --- a/libs/kstd/include/kstd/bits/format/args.hpp +++ b/libs/kstd/include/kstd/bits/format/args.hpp @@ -36,7 +36,7 @@ namespace kstd template constexpr auto determine_arg_type() -> arg_type { - using decay_type = std::remove_cvref_t>; + using decay_type = std::remove_cvref_t; if constexpr (std::same_as) { return arg_type::boolean; @@ -57,7 +57,8 @@ namespace kstd { return arg_type::string_view; } - else if constexpr (std::same_as || std::same_as) + else if constexpr (std::same_as, char *> || + std::same_as, char const *>) { return arg_type::c_string; } diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp index bff5f55..f391c8e 100644 --- a/libs/kstd/include/kstd/bits/format/formatter.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter.hpp @@ -7,7 +7,12 @@ namespace kstd { template - struct formatter; + struct formatter + { + formatter() = delete; + formatter(formatter const &) = delete; + auto operator=(formatter const &) -> formatter & = delete; + }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp index bf52f2e..9afb974 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp @@ -5,7 +5,6 @@ #include "../formatter.hpp" #include "string_view.hpp" -#include #include namespace kstd @@ -25,11 +24,6 @@ namespace kstd { }; - template - struct formatter : formatter // NOLINT - { - }; - } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp index edeaed1..40282e4 100644 --- a/libs/kstd/include/kstd/bits/format/string.hpp +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -28,6 +28,7 @@ namespace kstd if (current_index == target_index && !found) { using decay_type = std::remove_cvref_t; + static_assert(std::is_default_constructible_v>, "Missing formatter specialization."); auto fmt = formatter{}; auto it = fmt.parse(context); -- cgit v1.2.3 From f59a839ad60e941697797a9e3a81a5098b2b945f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:28:22 +0100 Subject: kstd/format: support range formatting --- libs/kstd/include/kstd/bits/format/formatter.hpp | 73 ++++++++++++++++++++++ .../include/kstd/bits/format/formatter/range.hpp | 32 ++++++++++ libs/kstd/include/kstd/format | 1 + 3 files changed, 106 insertions(+) create mode 100644 libs/kstd/include/kstd/bits/format/formatter/range.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp index f391c8e..096168d 100644 --- a/libs/kstd/include/kstd/bits/format/formatter.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter.hpp @@ -3,6 +3,14 @@ // IWYU pragma: private, include +#include "context.hpp" +#include "error.hpp" +#include "parse_context.hpp" + +#include +#include +#include + namespace kstd { @@ -14,6 +22,71 @@ namespace kstd auto operator=(formatter const &) -> formatter & = delete; }; + template + struct range_formatter + { + constexpr auto set_separator(std::string_view sep) -> void + { + m_separator = sep; + } + + constexpr auto set_brackets(std::string_view opening, std::string_view closing) + { + m_prefix = opening; + m_suffix = closing; + } + + constexpr auto parse(format_parse_context & context) + { + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == 'n') + { + set_brackets("", ""); + std::advance(it, 1); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + context.advance_to(it); + it = m_inner_formatter.parse(context); + } + + if (it != end && *it != '}') + { + bits::format::error("Invalid formate specifier for range"); + } + + return it; + } + + template + auto format(Range const & range, format_context & context) + { + context.push(m_prefix); + + auto is_first = true; + std::ranges::for_each(range, [&](auto const & element) { + if (!is_first) + { + context.push(m_separator); + } + m_inner_formatter.format(element, context); + is_first = false; + }); + + context.push(m_suffix); + } + + private: + kstd::formatter m_inner_formatter{}; + std::string_view m_separator{", "}; + std::string_view m_prefix{"["}; + std::string_view m_suffix{"]"}; + }; + } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/range.hpp b/libs/kstd/include/kstd/bits/format/formatter/range.hpp new file mode 100644 index 0000000..54ee7fb --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/range.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP + +#include "../formatter.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + namespace bits::format + { + template + concept iterable = requires(T const & t) { + t.begin(); + t.end(); + }; + + template + concept formattable_range = iterable && !std::same_as, std::string_view> && + !std::same_as, char *> && !std::same_as, char const *>; + } // namespace bits::format + + template + struct formatter : range_formatter> + { + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index 76aaed8..946ed69 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -10,6 +10,7 @@ #include "bits/format/formatter/integral.hpp" // IWYU pragma: export #include "bits/format/formatter/ordering.hpp" // IWYU pragma: export #include "bits/format/formatter/pointer.hpp" // IWYU pragma: export +#include "bits/format/formatter/range.hpp" // IWYU pragma: export #include "bits/format/formatter/string_view.hpp" // IWYU pragma: export #include "bits/format/parse_context.hpp" // IWYU pragma: export #include "bits/format/string.hpp" // IWYU pragma: export -- cgit v1.2.3 From 3e8efb0d65c32556d4a9cb603966beacfd61b29d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:35:46 +0100 Subject: kstd/format: add support for std::byte --- .../include/kstd/bits/format/formatter/byte.hpp | 23 ++++++++++++++++++++++ libs/kstd/include/kstd/format | 1 + 2 files changed, 24 insertions(+) create mode 100644 libs/kstd/include/kstd/bits/format/formatter/byte.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp new file mode 100644 index 0000000..70d98f4 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "integral.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(std::byte value, format_context & context) const -> void + { + formatter::format(static_cast(value), context); + } + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index 946ed69..adee5f8 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -6,6 +6,7 @@ #include "bits/format/context.hpp" // IWYU pragma: export #include "bits/format/formatter.hpp" // IWYU pragma: export #include "bits/format/formatter/bool.hpp" // IWYU pragma: export +#include "bits/format/formatter/byte.hpp" // IWYU pragma: export #include "bits/format/formatter/cstring.hpp" // IWYU pragma: export #include "bits/format/formatter/integral.hpp" // IWYU pragma: export #include "bits/format/formatter/ordering.hpp" // IWYU pragma: export -- cgit v1.2.3 From cd6bff48ab828f0a1c5b6a1a36f8eec81f0eb81f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:56:35 +0100 Subject: kstd/vector: fix rbegin and rend --- libs/kstd/include/kstd/vector | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 79530d2..d66c63b 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -462,13 +462,13 @@ namespace kstd //! Get a reverse iterator to the reverse beginning. [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator { - return empty() ? rend() : reverse_iterator{begin() + (m_size - 1)}; + 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{begin() + (m_size - 1)}; + return empty() ? rend() : const_reverse_iterator{end()}; } //! Get a reverse iterator to the reverse beginning. @@ -480,13 +480,13 @@ namespace kstd //! Get a reverse iterator to the reverse end. [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator { - return reverse_iterator{capacity() ? data() - 1 : nullptr}; + return reverse_iterator{begin()}; } //! Get a reverse iterator to the reverse end. [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { - return const_reverse_iterator{capacity() ? data() - 1 : nullptr}; + return const_reverse_iterator{begin()}; } //! Get a reverse iterator to the reverse end. -- cgit v1.2.3 From 9433102e33dae5697ca71c1d9116791fa933c974 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:58:03 +0100 Subject: kstd/vector: add missing constexpr --- libs/kstd/include/kstd/vector | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index d66c63b..2d148e4 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -384,7 +384,7 @@ namespace kstd //! Get a reference to the first element of this vector. //! //! The behavior is undefined if this vector is empty. - [[nodiscard]] auto front() const -> const_reference + [[nodiscard]] constexpr auto front() const -> const_reference { return *begin(); } @@ -484,13 +484,13 @@ namespace kstd } //! Get a reverse iterator to the reverse end. - [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator + [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator { return const_reverse_iterator{begin()}; } //! Get a reverse iterator to the reverse end. - [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator + [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator { return rend(); } @@ -597,7 +597,7 @@ namespace kstd //! Remove the last element of this vector. //! //! If this vector is empty, the behavior is undefined. - auto pop_back() -> void + constexpr auto pop_back() -> void { --m_size; std::allocator_traits::destroy(m_allocator, data() + size()); -- cgit v1.2.3 From cb01bffde672cf7f4bec3a1c5b6383241181c960 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 16:59:46 +0100 Subject: kstd/vector: relax move constructor --- libs/kstd/include/kstd/vector | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 2d148e4..0a3d6b7 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -154,7 +154,7 @@ namespace kstd //! //! @param other The source vector. constexpr vector(vector && other) noexcept - : m_allocator{std::move(std::exchange(other.m_allocator, allocator_type{}))} + : 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)) -- cgit v1.2.3 From b430f23711071872ff054a1e1b30f8a028584fe4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 17:01:04 +0100 Subject: kstd/vector: optimize clear --- libs/kstd/include/kstd/vector | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 0a3d6b7..e4979e5 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -562,10 +562,8 @@ namespace kstd //! Clear the contents of this vector. constexpr auto clear() noexcept -> void { - for (auto i = m_size; i > 0; --i) - { - pop_back(); - } + destroy_n(begin(), size()); + m_size = 0; } //! Append a given element to this vector via copy construction. -- cgit v1.2.3 From fb366b3c59941ea4c8e0a8d6e29ba0263f89bb02 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 17:09:39 +0100 Subject: kstd/vector: allow input iterators for construction --- libs/kstd/include/kstd/vector | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index e4979e5..7042ff1 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -100,7 +100,7 @@ namespace kstd //! 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. + //! @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 @@ -118,6 +118,27 @@ namespace kstd } } + //! 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. //! //! @@ -747,6 +768,12 @@ namespace kstd 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>; -- cgit v1.2.3 From 2f9e3917ef86ac0b00a517394df8a903c97770e1 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 17:16:16 +0100 Subject: kstd/vector: allow self-referential pushes Previously, calling `v.push_back(v.front())` might have resulted in undefined behavior if reallocation needed to occur. This patch provides for this and allows self-referential pushes. --- libs/kstd/include/kstd/vector | 60 ++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 15 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 7042ff1..a49572b 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -590,16 +590,42 @@ namespace kstd //! Append a given element to this vector via copy construction. constexpr auto push_back(value_type const & value) -> void { - increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, data() + size(), value); + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + std::allocator_traits::construct(m_allocator, new_data + m_size, value); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), value); + } ++m_size; } //! Append a given element to this vector via move construction. constexpr auto push_back(value_type && value) -> void { - increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + std::allocator_traits::construct(m_allocator, new_data + m_size, std::move(value)); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); + } ++m_size; } @@ -607,8 +633,21 @@ namespace kstd template constexpr auto emplace_back(Args &&... args) -> reference { - increase_capacity_if_full(); - std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + std::allocator_traits::construct(m_allocator, new_data + m_size, std::forward(args)...); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); + } ++m_size; return this->back(); } @@ -675,15 +714,6 @@ namespace kstd }); } - //! Check if there is still room in this vector, and if not allocate more space. - constexpr auto increase_capacity_if_full() -> void - { - if (m_size == m_capacity) - { - reserve(m_capacity == 0U ? 1U : m_capacity * 2U); - } - } - //! Move a number of elements from one storage location to another. //! //! @param from The start of the source range. -- cgit v1.2.3 From 52bc2d4105ac0d10d10831d470e9d0212dbb2b4d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 17:39:00 +0100 Subject: libs: fix header globs --- libs/elf/CMakeLists.txt | 6 +++--- libs/kstd/CMakeLists.txt | 2 +- libs/multiboot2/CMakeLists.txt | 10 +++------- 3 files changed, 7 insertions(+), 11 deletions(-) (limited to 'libs') diff --git a/libs/elf/CMakeLists.txt b/libs/elf/CMakeLists.txt index 66e59ee..f254094 100644 --- a/libs/elf/CMakeLists.txt +++ b/libs/elf/CMakeLists.txt @@ -1,13 +1,13 @@ add_library("elf" INTERFACE) add_library("libs::elf" ALIAS "elf") +file(GLOB_RECURSE ELF_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") + target_sources("elf" INTERFACE FILE_SET HEADERS BASE_DIRS "include" FILES - "include/elf/format.hpp" - "include/elf/section_header.hpp" - + ${ELF_HEADERS} ) target_include_directories("elf" INTERFACE diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 877a04e..b0c9c63 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -17,7 +17,7 @@ target_sources("kstd" PRIVATE "src/mutex.cpp" ) -file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") +file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*") target_sources("kstd" PUBLIC FILE_SET HEADERS diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt index b306b66..7384a3d 100644 --- a/libs/multiboot2/CMakeLists.txt +++ b/libs/multiboot2/CMakeLists.txt @@ -1,17 +1,13 @@ add_library("multiboot2" INTERFACE) add_library("libs::multiboot2" ALIAS "multiboot2") +file(GLOB_RECURSE MULTIBOOT2_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") + target_sources("multiboot2" INTERFACE FILE_SET HEADERS BASE_DIRS "include" FILES - "include/multiboot2/constants.hpp" - "include/multiboot2/information.hpp" - - "include/multiboot2/impl/data.hpp" - "include/multiboot2/impl/ids.hpp" - "include/multiboot2/impl/iterator.hpp" - "include/multiboot2/impl/tag.hpp" + ${MULTIBOOT2_HEADERS} ) target_include_directories("multiboot2" INTERFACE -- cgit v1.2.3 From 96f1511dbe2e80223732bcbef8068c3d5a330cee Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 18:08:41 +0100 Subject: kstd/vector: add missing constexpr clang-tidy is not happy about constexpr memory allocation except through the blessed std::allocator::allocate though. So for now we can't use it since it will break the build when linting is enabled. --- libs/kstd/include/kstd/vector | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index a49572b..b2cce0b 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -50,7 +50,7 @@ namespace kstd using const_reverse_iterator = std::reverse_iterator; //! Construct a new, empty vector. - vector() noexcept(std::is_nothrow_default_constructible_v) + constexpr vector() noexcept(std::is_nothrow_default_constructible_v) : vector(allocator_type{}) {} @@ -697,10 +697,13 @@ namespace kstd //! Release the memory of this vector. constexpr auto deallocate() { - std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); - m_capacity = 0; - m_size = 0; - m_data = nullptr; + if (m_data) + { + std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); + m_capacity = 0; + m_size = 0; + m_data = nullptr; + } } //! Destroy a number of elements in this vector. -- cgit v1.2.3 From 754012dd458985a6a4953c99204c6651318892b2 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 08:10:49 +0100 Subject: testing: enable build-host testing --- libs/kstd/CMakeLists.txt | 48 +++++++++++++++++++++++++++++++++++---------- libs/kstd/tests/os_mock.cpp | 15 ++++++++++++++ libs/kstd/tests/vector.cpp | 11 +++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 libs/kstd/tests/os_mock.cpp create mode 100644 libs/kstd/tests/vector.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index b0c9c63..2f360cd 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -1,16 +1,22 @@ add_library("kstd" STATIC) add_library("libs::kstd" ALIAS "kstd") -set(KSTD_LIBC_SYMBOLS - "abort" - "strlen" - "memcmp" - "memcpy" -) +if(CMAKE_CROSSCOMPILING) + set(KSTD_LIBC_SYMBOLS + "abort" + "strlen" + "memcmp" + "memcpy" + ) + + set(KSTD_LIBC_SOURCES + "src/libc/stdlib.cpp" + "src/libc/string.cpp" + ) +endif() target_sources("kstd" PRIVATE - "src/libc/stdlib.cpp" - "src/libc/string.cpp" + ${KSTD_LIBC_SOURCES} "src/os/error.cpp" @@ -30,6 +36,28 @@ target_include_directories("kstd" PUBLIC "include" ) -list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=") +if(CMAKE_CROSSCOMPILING) + list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=") + + target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) +endif() + +if(NOT CMAKE_CROSSCOMPILING) + add_executable("kstd_tests" + "tests/vector.cpp" + "tests/os_mock.cpp" + ) + + target_link_libraries("kstd_tests" PRIVATE + "Catch2::Catch2WithMain" + "libs::kstd" + ) + + set_target_properties("kstd_tests" PROPERTIES + C_CLANG_TIDY "" + CXX_CLANG_TIDY "" + EXCLUDE_FROM_ALL NO + ) -target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) \ No newline at end of file + catch_discover_tests("kstd_tests") +endif() \ No newline at end of file diff --git a/libs/kstd/tests/os_mock.cpp b/libs/kstd/tests/os_mock.cpp new file mode 100644 index 0000000..39b7f0d --- /dev/null +++ b/libs/kstd/tests/os_mock.cpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include + +namespace kstd::os +{ + auto panic(std::string_view message, std::source_location location) + { + auto full_message = + std::format("OS Panic Handler called '{}' at {}:{}", message, location.file_name(), location.line()); + throw std::runtime_error{full_message}; + } +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/tests/vector.cpp b/libs/kstd/tests/vector.cpp new file mode 100644 index 0000000..3a45008 --- /dev/null +++ b/libs/kstd/tests/vector.cpp @@ -0,0 +1,11 @@ +#include + +#include + +TEST_CASE("Creating an empty vector") +{ + kstd::vector v; + REQUIRE(v.empty()); + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == 0); +} -- cgit v1.2.3 From 48a2c33d205397adeaad385aebc1d1e008915b3e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 10:32:15 +0100 Subject: ci: enable test builds --- libs/kstd/CMakeLists.txt | 11 +- libs/kstd/tests/include/kstd/tests/os_panic.hpp | 23 ++ libs/kstd/tests/os_mock.cpp | 15 - libs/kstd/tests/src/os_panic.cpp | 15 + libs/kstd/tests/src/vector.cpp | 395 ++++++++++++++++++++++++ libs/kstd/tests/vector.cpp | 11 - 6 files changed, 442 insertions(+), 28 deletions(-) create mode 100644 libs/kstd/tests/include/kstd/tests/os_panic.hpp delete mode 100644 libs/kstd/tests/os_mock.cpp create mode 100644 libs/kstd/tests/src/os_panic.cpp create mode 100644 libs/kstd/tests/src/vector.cpp delete mode 100644 libs/kstd/tests/vector.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 2f360cd..06543ab 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -44,8 +44,12 @@ endif() if(NOT CMAKE_CROSSCOMPILING) add_executable("kstd_tests" - "tests/vector.cpp" - "tests/os_mock.cpp" + "tests/src/vector.cpp" + "tests/src/os_panic.cpp" + ) + + target_include_directories("kstd_tests" PRIVATE + "tests/include" ) target_link_libraries("kstd_tests" PRIVATE @@ -59,5 +63,8 @@ if(NOT CMAKE_CROSSCOMPILING) EXCLUDE_FROM_ALL NO ) + enable_coverage("kstd") + enable_coverage("kstd_tests") + catch_discover_tests("kstd_tests") endif() \ No newline at end of file diff --git a/libs/kstd/tests/include/kstd/tests/os_panic.hpp b/libs/kstd/tests/include/kstd/tests/os_panic.hpp new file mode 100644 index 0000000..4396a9f --- /dev/null +++ b/libs/kstd/tests/include/kstd/tests/os_panic.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_TESTS_OS_PANIC_HPP +#define KSTD_TESTS_OS_PANIC_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + struct os_panic : std::runtime_error + { + os_panic(std::string message, std::source_location location) + : std::runtime_error{message} + , location(location) + {} + + std::source_location location; + }; + +} // namespace kstd::tests + +#endif \ No newline at end of file diff --git a/libs/kstd/tests/os_mock.cpp b/libs/kstd/tests/os_mock.cpp deleted file mode 100644 index 39b7f0d..0000000 --- a/libs/kstd/tests/os_mock.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include -#include -#include -#include - -namespace kstd::os -{ - auto panic(std::string_view message, std::source_location location) - { - auto full_message = - std::format("OS Panic Handler called '{}' at {}:{}", message, location.file_name(), location.line()); - throw std::runtime_error{full_message}; - } -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/tests/src/os_panic.cpp b/libs/kstd/tests/src/os_panic.cpp new file mode 100644 index 0000000..3eae6ff --- /dev/null +++ b/libs/kstd/tests/src/os_panic.cpp @@ -0,0 +1,15 @@ +#include "kstd/tests/os_panic.hpp" + +#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/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp new file mode 100644 index 0000000..cb1b182 --- /dev/null +++ b/libs/kstd/tests/src/vector.cpp @@ -0,0 +1,395 @@ +#include "kstd/tests/os_panic.hpp" + +#include +#include + +#include + +#include +#include +#include + +SCENARIO("Vector initialization and construction", "[vector]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + kstd::vector v; + + 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") + { + kstd::vector v(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") + { + kstd::vector v = {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") + { + kstd::vector source = {1, 2, 3, 4, 5}; + + WHEN("copy constructing a new vector") + { + kstd::vector copy(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") + { + kstd::vector moved(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") + { + kstd::vector v = {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") + { + kstd::vector v = {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") + { + kstd::vector v; + + 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") + { + kstd::vector v; + + 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()); + } + } + } + + GIVEN("A populated vector with excess capacity") + { + kstd::vector v{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); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/tests/vector.cpp b/libs/kstd/tests/vector.cpp deleted file mode 100644 index 3a45008..0000000 --- a/libs/kstd/tests/vector.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -#include - -TEST_CASE("Creating an empty vector") -{ - kstd::vector v; - REQUIRE(v.empty()); - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == 0); -} -- cgit v1.2.3 From bb7a6bd989b748a29c1a1e68807da2dd3176186a Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 11:19:30 +0100 Subject: kstd: fix push/emplace_back bug in vector --- libs/kstd/include/kstd/vector | 6 ++ libs/kstd/tests/src/vector.cpp | 168 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index b2cce0b..1242489 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -594,12 +594,14 @@ namespace kstd { 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 + m_size, value); uninitialized_move_with_allocator(begin(), new_data, size()); destroy_n(begin(), size()); deallocate(); m_data = new_data; m_capacity = new_capacity; + m_size = old_size; } else { @@ -615,12 +617,14 @@ namespace kstd { 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 + m_size, std::move(value)); uninitialized_move_with_allocator(begin(), new_data, size()); destroy_n(begin(), size()); deallocate(); m_data = new_data; m_capacity = new_capacity; + m_size = old_size; } else { @@ -637,12 +641,14 @@ namespace kstd { 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 + m_size, std::forward(args)...); uninitialized_move_with_allocator(begin(), new_data, size()); destroy_n(begin(), size()); deallocate(); m_data = new_data; m_capacity = new_capacity; + m_size = old_size; } else { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index cb1b182..2440247 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -392,4 +392,172 @@ SCENARIO("Vector capacity management", "[vector]") } } } +} + +SCENARIO("Vector modifiers", "[vector]") +{ + GIVEN("An empty vector") + { + kstd::vector v; + + 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); + } + } + } + + GIVEN("A populated vector") + { + kstd::vector v = {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("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); + } + } + } +} + +SCENARIO("Vector comparison", "[vector]") +{ + GIVEN("Two identical vectors") + { + kstd::vector v1 = {1, 2, 3}; + kstd::vector v2 = {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") + { + kstd::vector v1 = {1, 2, 3}; + kstd::vector v2 = {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") + { + kstd::vector v1 = {1, 2, 3}; + kstd::vector v2 = {1, 2, 4}; + + WHEN("comparing for ordering") + { + THEN("they are ordered lexicographically") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } } \ No newline at end of file -- cgit v1.2.3 From a2a407efd453635af9ff6d3514114bfee95bcbb9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 11:40:44 +0100 Subject: kstd/vector: add more tests for different types --- libs/kstd/tests/src/vector.cpp | 186 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) (limited to 'libs') diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 2440247..1427ce8 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -560,4 +561,189 @@ SCENARIO("Vector comparison", "[vector]") } } } +} + +struct non_default_constructible +{ + int value; + non_default_constructible() = delete; + explicit non_default_constructible(int v) + : value{v} + {} + + bool operator==(non_default_constructible const & other) const noexcept = default; +}; + +SCENARIO("Vector with non-default-constructible types", "[vector]") +{ + GIVEN("A type without a default constructor") + { + WHEN("constructing an empty vector") + { + kstd::vector v; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("using emplace_back") + { + kstd::vector v; + v.emplace_back(42); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back() == non_default_constructible{42}); + } + } + } +} + +template +struct tracking_allocator +{ + using value_type = T; + + int * allocation_count; + + tracking_allocator(int * counter) + : allocation_count{counter} + {} + + template + tracking_allocator(tracking_allocator const & other) noexcept + : allocation_count{other.allocation_count} + {} + + T * allocate(std::size_t n) + { + if (allocation_count) + { + (*allocation_count)++; + } + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T * p, std::size_t n) noexcept + { + ::operator delete(p, n * sizeof(T)); + } + + bool operator==(tracking_allocator const & other) + { + return allocation_count == other.allocation_count; + } +}; + +SCENARIO("Vector with custom allocator", "[vector]") +{ + GIVEN("a tracking allocator acting as the vector's memory manager") + { + auto allocations = 0; + tracking_allocator allocator{&allocations}; + + WHEN("a vector uses this allocator to allocate memory") + { + kstd::vector> v{allocator}; + REQUIRE(allocations == 0); + + v.reserve(10); + + THEN("the allocator was used to allocate memory") + { + REQUIRE(allocations > 0); + } + } + } +} + +struct move_tracker +{ + int value; + bool was_copied{}; + bool was_moved{}; + + move_tracker() = default; + explicit move_tracker(int v) + : value{v} + {} + + move_tracker(move_tracker const & other) + : value{other.value} + , was_copied{true} + , was_moved{false} + {} + + move_tracker(move_tracker && other) noexcept + : value{other.value} + , was_copied{false} + , was_moved{true} + { + other.value = -1; + } + + move_tracker & operator=(move_tracker const & other) + { + value = other.value; + was_copied = true; + was_moved = false; + return *this; + } + + move_tracker & operator=(move_tracker && other) noexcept + { + value = other.value; + was_moved = true; + was_copied = false; + other.value = -1; + return *this; + } +}; + +SCENARIO("Vector modifier move semantics", "[vector]") +{ + GIVEN("An empty vector and a move tracker element") + { + kstd::vector v; + move_tracker tracker{42}; + + 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().was_moved); + REQUIRE_FALSE(v.back().was_copied); + REQUIRE(v.back().value == 42); + } + + THEN("the original tracker is left in a valid but unspecified state") + { + REQUIRE(tracker.value == -1); + } + } + } + + GIVEN("An empty vector") + { + kstd::vector v; + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(42); + + THEN("the element is constructed directly, without moves or copies") + { + REQUIRE(v.size() == 1); + REQUIRE_FALSE(v.back().was_moved); + REQUIRE_FALSE(v.back().was_copied); + REQUIRE(v.back().value == 42); + } + } + } } \ No newline at end of file -- cgit v1.2.3 From d31d12a612e9bbb1eb32ba7bcb17d70ff917ac9e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 12:19:23 +0100 Subject: kstd/vector: expand tests --- libs/kstd/include/kstd/vector | 7 +- libs/kstd/tests/src/vector.cpp | 511 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 488 insertions(+), 30 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 1242489..5655854 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -60,6 +60,9 @@ namespace kstd 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. @@ -237,7 +240,7 @@ namespace kstd //! 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 & + constexpr auto operator=(vector const & other) -> vector & { if (this == std::addressof(other)) { @@ -285,7 +288,7 @@ namespace kstd //! @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 & + std::allocator_traits::is_always_equal::value) -> vector & { using std::swap; diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 1427ce8..713f6ab 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -16,7 +17,7 @@ SCENARIO("Vector initialization and construction", "[vector]") { WHEN("constructing by default") { - kstd::vector v; + auto v = kstd::vector{}; THEN("the vector is empty") { @@ -32,7 +33,7 @@ SCENARIO("Vector initialization and construction", "[vector]") WHEN("constructing with a specific size") { - kstd::vector v(10); + auto v = kstd::vector(10); THEN("the vector is not empty") { @@ -48,7 +49,7 @@ SCENARIO("Vector initialization and construction", "[vector]") WHEN("constructing from an initializer list") { - kstd::vector v = {1, 2, 3, 4, 5}; + auto v = kstd::vector{1, 2, 3, 4, 5}; THEN("the vector is not empty") { @@ -125,11 +126,11 @@ SCENARIO("Vector initialization and construction", "[vector]") GIVEN("A populated vector") { - kstd::vector source = {1, 2, 3, 4, 5}; + auto source = kstd::vector{1, 2, 3, 4, 5}; WHEN("copy constructing a new vector") { - kstd::vector copy(source); + auto copy = kstd::vector(source); THEN("the copy matches the original") { @@ -156,7 +157,7 @@ SCENARIO("Vector initialization and construction", "[vector]") WHEN("move constructing a new vector") { - kstd::vector moved(std::move(source)); + auto moved = kstd::vector(std::move(source)); THEN("The new vector has the original elements") { @@ -183,7 +184,7 @@ SCENARIO("Vector element access", "[vector]") { GIVEN("A populated vector") { - kstd::vector v = {10, 20, 30}; + auto v = kstd::vector{10, 20, 30}; WHEN("accessing elements for reading") { @@ -239,7 +240,7 @@ SCENARIO("Vector iterators", "[vector]") { GIVEN("A populated vector") { - kstd::vector v = {1, 2, 3}; + auto v = kstd::vector{1, 2, 3}; WHEN("using forward iterators") { @@ -322,7 +323,7 @@ SCENARIO("Vector iterators", "[vector]") GIVEN("an empty vector") { - kstd::vector v; + auto v = kstd::vector{}; WHEN("getting iterators") { @@ -345,7 +346,7 @@ SCENARIO("Vector capacity management", "[vector]") { GIVEN("An empty vector") { - kstd::vector v; + auto v = kstd::vector{}; WHEN("reserving space") { @@ -366,11 +367,31 @@ SCENARIO("Vector capacity management", "[vector]") 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") { - kstd::vector v{1, 2, 3}; + auto v = kstd::vector{1, 2, 3}; v.reserve(10); REQUIRE(v.capacity() == 10); @@ -399,7 +420,7 @@ SCENARIO("Vector modifiers", "[vector]") { GIVEN("An empty vector") { - kstd::vector v; + auto v = kstd::vector{}; WHEN("push_back is called with a value") { @@ -424,11 +445,28 @@ SCENARIO("Vector modifiers", "[vector]") 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); + } + } } GIVEN("A populated vector") { - kstd::vector v = {10, 20, 30}; + auto v = kstd::vector{10, 20, 30}; auto initial_capacity = v.capacity(); WHEN("push_back is called") @@ -461,6 +499,21 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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(); @@ -492,8 +545,8 @@ SCENARIO("Vector comparison", "[vector]") { GIVEN("Two identical vectors") { - kstd::vector v1 = {1, 2, 3}; - kstd::vector v2 = {1, 2, 3}; + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3}; WHEN("comparing for equality") { @@ -521,8 +574,8 @@ SCENARIO("Vector comparison", "[vector]") GIVEN("Two vectors of different sizes") { - kstd::vector v1 = {1, 2, 3}; - kstd::vector v2 = {1, 2, 3, 4}; + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3, 4}; WHEN("comparing for equality") { @@ -549,8 +602,8 @@ SCENARIO("Vector comparison", "[vector]") GIVEN("Two vectors of the same size but different elements") { - kstd::vector v1 = {1, 2, 3}; - kstd::vector v2 = {1, 2, 4}; + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 4}; WHEN("comparing for ordering") { @@ -580,7 +633,7 @@ SCENARIO("Vector with non-default-constructible types", "[vector]") { WHEN("constructing an empty vector") { - kstd::vector v; + auto v = kstd::vector{}; THEN("the vector is empty") { @@ -590,7 +643,7 @@ SCENARIO("Vector with non-default-constructible types", "[vector]") WHEN("using emplace_back") { - kstd::vector v; + auto v = kstd::vector{}; v.emplace_back(42); THEN("the element is added and the size and capacity increase") @@ -632,10 +685,29 @@ struct tracking_allocator ::operator delete(p, n * sizeof(T)); } - bool operator==(tracking_allocator const & other) + bool operator==(tracking_allocator const & other) const { return allocation_count == other.allocation_count; } + + bool operator!=(tracking_allocator const & other) const + { + return allocation_count != other.allocation_count; + } +}; + +template +struct propagating_allocator : tracking_allocator +{ + using propagate_on_container_copy_assignment = std::true_type; + propagating_allocator(int * counter) + : tracking_allocator{counter} + {} + + template + propagating_allocator(propagating_allocator const & other) noexcept + : tracking_allocator{other.allocation_count} + {} }; SCENARIO("Vector with custom allocator", "[vector]") @@ -643,11 +715,11 @@ SCENARIO("Vector with custom allocator", "[vector]") GIVEN("a tracking allocator acting as the vector's memory manager") { auto allocations = 0; - tracking_allocator allocator{&allocations}; + auto allocator = tracking_allocator{&allocations}; WHEN("a vector uses this allocator to allocate memory") { - kstd::vector> v{allocator}; + auto v = kstd::vector>(allocator); REQUIRE(allocations == 0); v.reserve(10); @@ -707,8 +779,8 @@ SCENARIO("Vector modifier move semantics", "[vector]") { GIVEN("An empty vector and a move tracker element") { - kstd::vector v; - move_tracker tracker{42}; + auto v = kstd::vector{}; + auto tracker = move_tracker{42}; WHEN("push_back is called with the move tracker") { @@ -731,7 +803,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") GIVEN("An empty vector") { - kstd::vector v; + auto v = kstd::vector{}; WHEN("emplace_back is called with constructor arguments") { @@ -746,4 +818,387 @@ SCENARIO("Vector modifier move semantics", "[vector]") } } } -} \ No newline at end of file +} + +struct test_input_iterator +{ + using iterator_concept = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = int; + + int const * current; + int operator*() const + { + return *current; + } + test_input_iterator & operator++() + { + ++current; + return *this; + } + void operator++(int) + { + ++current; + } + bool operator==(test_input_iterator const & other) const + { + return current == other.current; + } +}; + +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 = test_input_iterator{arr.data()}; + auto const last = test_input_iterator{arr.data() + arr.size()}; + + 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 = 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 = 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 = propagating_allocator{&allocs1}; + auto alloc2 = 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 = 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 = tracking_allocator{&allocs1}; + auto alloc2 = 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 = move_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().was_copied); + REQUIRE_FALSE(v.back().was_moved); + } + } + + 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().was_copied); + REQUIRE_FALSE(v.back().was_moved); + } + } + } +} -- cgit v1.2.3 From f7b065d72684526aedd580cb564f6d010653a22e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 23 Mar 2026 12:48:52 +0100 Subject: kstd/tests: extract test helper types --- libs/kstd/tests/include/kstd/tests/test_types.hpp | 251 ++++++++++++++++++++++ libs/kstd/tests/src/vector.cpp | 199 +++-------------- 2 files changed, 283 insertions(+), 167 deletions(-) create mode 100644 libs/kstd/tests/include/kstd/tests/test_types.hpp (limited to 'libs') diff --git a/libs/kstd/tests/include/kstd/tests/test_types.hpp b/libs/kstd/tests/include/kstd/tests/test_types.hpp new file mode 100644 index 0000000..6a06311 --- /dev/null +++ b/libs/kstd/tests/include/kstd/tests/test_types.hpp @@ -0,0 +1,251 @@ +#ifndef KSTD_TESTS_TEST_TYPES_HPP +#define KSTD_TESTS_TEST_TYPES_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + //! A type tracking copy and move operations + //! + //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. + struct move_tracker + { + //! A value indicating that the object was moved from. + constexpr auto static moved_from_v = -1; + + //! A simple value to be able to track the move-from state. + int value{}; + //! A flag to track if an instance of this type was either copy constructed or copy assigned. + bool was_copied{false}; + //! A flag to track if an instance of this type was either move constructed or move assigned. + bool was_moved{false}; + + //! Construct a new move tracker with the given value, if any. + constexpr move_tracker(int v = 0) + : value{v} + {} + + //! Construct a new move tracker by copying an existing one. + constexpr move_tracker(move_tracker const & other) + : value{other.value} + , was_copied{true} + {} + + //! Construct a new move tracker by moving from an existing one. + constexpr move_tracker(move_tracker && other) noexcept + : value{other.value} + , was_moved{true} + { + other.value = moved_from_v; + } + + //! Copy assign a new move tracker from an existing one. + constexpr auto operator=(move_tracker const & other) -> move_tracker & + { + if (this != &other) + { + value = other.value; + was_copied = true; + } + return *this; + } + + //! Move assign a new move tracker from an existing one. + //! + //! This function ensures that the moved-from state is marked. + constexpr auto operator=(move_tracker && other) noexcept -> move_tracker & + { + if (this != &other) + { + value = other.value; + was_moved = true; + other.value = moved_from_v; + } + return *this; + } + }; + + //! A type that is not default constructible. + //! + //! This type is designed to test default construction semantics of standard library containers implemented in kstd. + struct non_default_constructible + { + //! A simple placeholder value. + int value{}; + + //! Construct a new non-default-constructible object with the given value. + constexpr explicit non_default_constructible(int v) + : value{v} + {} + + //! Compare two non-default-constructible objects for equality. + [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool + { + return value == other.value; + } + }; + + //! An allocator that tracks the number of allocations. + //! + //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct tracking_allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + //! A pointer to a counter that is incremented on allocation. + //! + //! A pointer is used so that multiple allocators can share the same counter. + int * allocation_count{}; + + //! Construct a new tracking allocator referencing the given counter. + tracking_allocator(int * counter) + : allocation_count{counter} + {} + + //! Construct a new tracking allocator by copying from another tracking allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + template + tracking_allocator(tracking_allocator const & other) noexcept + : allocation_count{other.allocation_count} + {} + + //! Allocate memory for n elements of type T. + //! + //! @param n The number of elements to allocate. + //! @return A pointer to the allocated memory. + [[nodiscard]] auto allocate(std::size_t n) -> T * + { + if (allocation_count != nullptr) + { + ++(*allocation_count); + } + return static_cast(::operator new(n * sizeof(T))); + } + + //! Deallocate memory for n elements of type T. + //! + //! @param p A pointer to the memory to deallocate. + //! @param n The number of elements to deallocate. + auto deallocate(T * p, std::size_t n) noexcept -> void + { + ::operator delete(p, n * sizeof(T)); + } + + //! Compare two tracking allocators for equality. + //! + //! Two allocators are considered equal if they reference the same allocation counter. + //! + //! @param other The other tracking allocator to compare to. + //! @return True if the two tracking allocators are equal, false otherwise. + [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool + { + return allocation_count == other.allocation_count; + } + + //! Compare two tracking_allocators for inequality. + //! + //! @param other The other tracking_allocator to compare to. + //! @return True if the two tracking_allocators are not equal, false otherwise. + [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool + { + return allocation_count != other.allocation_count; + } + }; + + //! An allocator that propagates copy assignment. + //! + //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct propagating_allocator : tracking_allocator + { + //! A flag to indicate that the allocator propagates copy assignment. + using propagate_on_container_copy_assignment = std::true_type; + + //! Construct a new propagating allocator referencing the given counter. + //! + //! @param counter A pointer to a counter that is incremented on allocation. + //! @see tracking_allocator::tracking_allocator(int*) + propagating_allocator(int * counter) + : tracking_allocator{counter} + {} + + //! Construct a new propagating allocator by copying from another propagating allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + //! @see tracking_allocator::tracking_allocator(tracking_allocator const&) + template + propagating_allocator(propagating_allocator const & other) noexcept + : tracking_allocator{other.allocation_count} + {} + }; + + //! A test input iterator. + //! + //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd. + struct test_input_iterator + { + using iterator_concept = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = int; + + //! The current element pointed to by the iterator. + int const * current; + + //! Construct a new test input iterator. + //! + //! @param current The current element pointed to by the iterator. + constexpr explicit test_input_iterator(int const * current) + : current{current} + {} + + //! Dereference the iterator to get the current element. + //! + //! @return The current element pointed to by the iterator. + [[nodiscard]] auto operator*() const -> int + { + return *current; + } + + //! Increment the iterator to point to the next element. + //! + //! @return A reference to the incremented iterator. + auto operator++() -> test_input_iterator & + { + ++current; + return *this; + } + + //! Increment the iterator to point to the next element. + auto operator++(int) -> void + { + ++*this; + } + + //! Compare two test input iterators for equality. + //! + //! @param other The other test input iterator to compare to. + //! @return True if the two test input iterators are equal, false otherwise. + [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool + { + return current == other.current; + } + }; + +} // namespace kstd::tests + +#endif diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 713f6ab..d4c5e4f 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -1,12 +1,12 @@ #include "kstd/tests/os_panic.hpp" #include +#include #include #include #include -#include #include #include #include @@ -616,24 +616,13 @@ SCENARIO("Vector comparison", "[vector]") } } -struct non_default_constructible -{ - int value; - non_default_constructible() = delete; - explicit non_default_constructible(int v) - : value{v} - {} - - bool operator==(non_default_constructible const & other) const noexcept = default; -}; - SCENARIO("Vector with non-default-constructible types", "[vector]") { GIVEN("A type without a default constructor") { WHEN("constructing an empty vector") { - auto v = kstd::vector{}; + auto v = kstd::vector{}; THEN("the vector is empty") { @@ -643,83 +632,28 @@ SCENARIO("Vector with non-default-constructible types", "[vector]") WHEN("using emplace_back") { - auto v = kstd::vector{}; + auto v = kstd::vector{}; v.emplace_back(42); THEN("the element is added and the size and capacity increase") { REQUIRE(v.size() == 1); - REQUIRE(v.back() == non_default_constructible{42}); + REQUIRE(v.back() == kstd::tests::non_default_constructible{42}); } } } } -template -struct tracking_allocator -{ - using value_type = T; - - int * allocation_count; - - tracking_allocator(int * counter) - : allocation_count{counter} - {} - - template - tracking_allocator(tracking_allocator const & other) noexcept - : allocation_count{other.allocation_count} - {} - - T * allocate(std::size_t n) - { - if (allocation_count) - { - (*allocation_count)++; - } - return static_cast(::operator new(n * sizeof(T))); - } - - void deallocate(T * p, std::size_t n) noexcept - { - ::operator delete(p, n * sizeof(T)); - } - - bool operator==(tracking_allocator const & other) const - { - return allocation_count == other.allocation_count; - } - - bool operator!=(tracking_allocator const & other) const - { - return allocation_count != other.allocation_count; - } -}; - -template -struct propagating_allocator : tracking_allocator -{ - using propagate_on_container_copy_assignment = std::true_type; - propagating_allocator(int * counter) - : tracking_allocator{counter} - {} - - template - propagating_allocator(propagating_allocator const & other) noexcept - : tracking_allocator{other.allocation_count} - {} -}; - SCENARIO("Vector with custom allocator", "[vector]") { GIVEN("a tracking allocator acting as the vector's memory manager") { auto allocations = 0; - auto allocator = tracking_allocator{&allocations}; + auto allocator = kstd::tests::tracking_allocator{&allocations}; WHEN("a vector uses this allocator to allocate memory") { - auto v = kstd::vector>(allocator); + auto v = kstd::vector>(allocator); REQUIRE(allocations == 0); v.reserve(10); @@ -732,55 +666,12 @@ SCENARIO("Vector with custom allocator", "[vector]") } } -struct move_tracker -{ - int value; - bool was_copied{}; - bool was_moved{}; - - move_tracker() = default; - explicit move_tracker(int v) - : value{v} - {} - - move_tracker(move_tracker const & other) - : value{other.value} - , was_copied{true} - , was_moved{false} - {} - - move_tracker(move_tracker && other) noexcept - : value{other.value} - , was_copied{false} - , was_moved{true} - { - other.value = -1; - } - - move_tracker & operator=(move_tracker const & other) - { - value = other.value; - was_copied = true; - was_moved = false; - return *this; - } - - move_tracker & operator=(move_tracker && other) noexcept - { - value = other.value; - was_moved = true; - was_copied = false; - other.value = -1; - return *this; - } -}; - SCENARIO("Vector modifier move semantics", "[vector]") { GIVEN("An empty vector and a move tracker element") { - auto v = kstd::vector{}; - auto tracker = move_tracker{42}; + auto v = kstd::vector{}; + auto tracker = kstd::tests::move_tracker{42}; WHEN("push_back is called with the move tracker") { @@ -803,7 +694,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") GIVEN("An empty vector") { - auto v = kstd::vector{}; + auto v = kstd::vector{}; WHEN("emplace_back is called with constructor arguments") { @@ -820,32 +711,6 @@ SCENARIO("Vector modifier move semantics", "[vector]") } } -struct test_input_iterator -{ - using iterator_concept = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = int; - - int const * current; - int operator*() const - { - return *current; - } - test_input_iterator & operator++() - { - ++current; - return *this; - } - void operator++(int) - { - ++current; - } - bool operator==(test_input_iterator const & other) const - { - return current == other.current; - } -}; - SCENARIO("Vector advanced construction", "[vector]") { GIVEN("A count and a default value") @@ -871,8 +736,8 @@ SCENARIO("Vector advanced construction", "[vector]") WHEN("constructing from input iterators") { auto const arr = std::array{1, 2, 3}; - auto const first = test_input_iterator{arr.data()}; - auto const last = test_input_iterator{arr.data() + arr.size()}; + auto const first = kstd::tests::test_input_iterator{arr.data()}; + auto const last = kstd::tests::test_input_iterator{arr.data() + arr.size()}; auto v = kstd::vector(first, last); @@ -888,8 +753,8 @@ SCENARIO("Vector advanced construction", "[vector]") GIVEN("A tracking allocator and a source vector") { auto allocs = 0; - auto allocator = tracking_allocator{&allocs}; - auto source = kstd::vector>{allocator}; + auto allocator = kstd::tests::tracking_allocator{&allocs}; + auto source = kstd::vector>{allocator}; source.push_back(1); source.push_back(2); source.push_back(3); @@ -898,7 +763,7 @@ SCENARIO("Vector advanced construction", "[vector]") WHEN("copy constructing with an allocator") { - auto copy = kstd::vector>(source, allocator); + auto copy = kstd::vector>(source, allocator); THEN("the copy succeeds and the allocator is used") { @@ -911,7 +776,7 @@ SCENARIO("Vector advanced construction", "[vector]") WHEN("move constructing with an identically comparing allocator") { - auto moved = kstd::vector>(std::move(source), allocator); + auto moved = kstd::vector>(std::move(source), allocator); THEN("the move succeeds and no new allocations are made (memory is stolen)") { @@ -925,9 +790,9 @@ SCENARIO("Vector advanced construction", "[vector]") WHEN("move constructing with a non-equal allocator") { auto allocs2 = 0; - auto allocator2 = tracking_allocator{&allocs2}; + auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; - auto moved = kstd::vector>(std::move(source), allocator2); + auto moved = kstd::vector>(std::move(source), allocator2); THEN("the move allocates new memory and moves elements") { @@ -977,13 +842,13 @@ SCENARIO("Vector assignment operators", "[vector]") { auto allocs1 = 0; auto allocs2 = 0; - auto alloc1 = propagating_allocator{&allocs1}; - auto alloc2 = propagating_allocator{&allocs2}; + auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; + auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; - auto v1 = kstd::vector>{alloc1}; + auto v1 = kstd::vector>{alloc1}; v1.push_back(1); v1.push_back(2); - auto v2 = kstd::vector>{alloc2}; + auto v2 = kstd::vector>{alloc2}; WHEN("copy assigning") { @@ -1028,10 +893,10 @@ SCENARIO("Vector assignment operators", "[vector]") GIVEN("Vectors with the same tracking allocator") { auto allocs = 0; - auto alloc = tracking_allocator{&allocs}; - auto v1 = kstd::vector>{alloc}; + auto alloc = kstd::tests::tracking_allocator{&allocs}; + auto v1 = kstd::vector>{alloc}; v1.push_back(1); - auto v2 = kstd::vector>{alloc}; + auto v2 = kstd::vector>{alloc}; WHEN("move assigning") { @@ -1048,15 +913,15 @@ SCENARIO("Vector assignment operators", "[vector]") { auto allocs1 = 0; auto allocs2 = 0; - auto alloc1 = tracking_allocator{&allocs1}; - auto alloc2 = tracking_allocator{&allocs2}; + auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; + auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; - auto v1 = kstd::vector>{alloc1}; + auto v1 = kstd::vector>{alloc1}; v1.push_back(1); v1.push_back(2); v1.push_back(3); - auto v2 = kstd::vector>{alloc2}; + auto v2 = kstd::vector>{alloc2}; v2.push_back(4); v2.push_back(5); @@ -1071,7 +936,7 @@ SCENARIO("Vector assignment operators", "[vector]") } } - auto v3 = kstd::vector>{alloc2}; + auto v3 = kstd::vector>{alloc2}; v3.reserve(10); v3.push_back(4); v3.push_back(5); @@ -1084,7 +949,7 @@ SCENARIO("Vector assignment operators", "[vector]") } } - auto v4 = kstd::vector>{alloc2}; + auto v4 = kstd::vector>{alloc2}; v4.reserve(10); v4.push_back(4); v4.push_back(5); @@ -1169,8 +1034,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]") GIVEN("An empty vector and a const lvalue tracker") { - auto v = kstd::vector{}; - auto const tracker = move_tracker{42}; + auto v = kstd::vector{}; + auto const tracker = kstd::tests::move_tracker{42}; WHEN("push_back is called with the const lvalue") { -- cgit v1.2.3 From f08512d1cddcf43de64f0b3f8b0e6c9fe31bcaea Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 24 Mar 2026 13:03:15 +0100 Subject: kstd/vector: add basic insert overloads --- libs/kstd/include/kstd/vector | 130 +++++++++++++++++++++++++-------- libs/kstd/tests/src/vector.cpp | 160 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 28 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 5655854..0d4aac8 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -259,7 +259,7 @@ namespace kstd if (capacity() >= other.size()) { auto const overlap = std::min(m_size, other.m_size); - copy_elements(other.begin(), begin(), overlap); + std::ranges::copy(other.begin(), other.begin() + overlap, begin()); if (m_size < other.m_size) { @@ -317,7 +317,7 @@ namespace kstd if (capacity() >= other.size()) { auto const overlap = std::min(size(), other.size()); - move_elements(other.begin(), begin(), overlap); + std::ranges::move(other.begin(), other.begin() + overlap, begin()); if (size() < other.size()) { @@ -590,6 +590,106 @@ namespace kstd 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 + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(value); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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, value); + 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; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = value; + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value; + } + + ++m_size; + return begin() + prefix_size; + } + + //! 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 + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(std::move(value)); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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::move(value)); + 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; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = std::move(value); + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value); + } + + ++m_size; + return begin() + prefix_size; + } + //! Append a given element to this vector via copy construction. constexpr auto push_back(value_type const & value) -> void { @@ -690,19 +790,6 @@ namespace kstd deallocate(); } - //! Copy a number of elements from one storage location to another. - //! - //! @param from The start of the source range. - //! @param to The start of the target range. - //! @param count The number of element to copy. - constexpr auto static copy_elements(const_iterator from, iterator to, size_type count) -> void - { - for (auto i = 0uz; i < count; ++i) - { - *to++ = *from++; - } - } - //! Release the memory of this vector. constexpr auto deallocate() { @@ -726,19 +813,6 @@ namespace kstd }); } - //! Move a number of elements from one storage location to another. - //! - //! @param from The start of the source range. - //! @param to The start of the target range. - //! @param count The number of element to copy. - constexpr auto static move_elements(iterator from, iterator to, size_t count) - { - for (auto i = 0uz; i < count; ++i) - { - *to++ = std::move(*from++); - } - } - //! Panic the kernel if the given index is out of bounds. //! //! @param index The index to check. diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index d4c5e4f..0735c9a 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -462,6 +462,19 @@ SCENARIO("Vector modifiers", "[vector]") REQUIRE(v[1] == 20); } } + + WHEN("inserting an element") + { + auto it = v.insert(v.cbegin(), 42); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 42); + REQUIRE(it == v.begin()); + } + } } GIVEN("A populated vector") @@ -538,6 +551,104 @@ SCENARIO("Vector modifiers", "[vector]") 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 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); + } + } } } @@ -709,6 +820,55 @@ SCENARIO("Vector modifier move semantics", "[vector]") } } } + + 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::move_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_FALSE(v[0].was_moved); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].was_copied); + REQUIRE_FALSE(v[1].was_moved); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].was_moved); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].was_moved); + } + } + + WHEN("inserting an rvalue element in the middle with sufficient capacity") + { + auto tracker = kstd::tests::move_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_FALSE(v[0].was_moved); + REQUIRE(v[1].value == 15); + REQUIRE_FALSE(v[1].was_copied); + REQUIRE(v[1].was_moved); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].was_moved); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].was_moved); + } + } + } } SCENARIO("Vector advanced construction", "[vector]") -- cgit v1.2.3 From 09bf8eba8dbc76dc9c46ec1486cbf2f9530233a8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 10:44:05 +0100 Subject: implement simple weak_ptr and enable_shared_from_this --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 320 ++++++++++++++++++++++++++--- 1 file changed, 286 insertions(+), 34 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index 6bce83f..674807d 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -10,9 +10,199 @@ namespace kstd { + /** + * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared + * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed + * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. + * The weak_count is needed to determine when it is safe to delete the shared_control_block itself + */ + struct shared_control_block + { + std::atomic shared_count; + std::atomic weak_count; + + explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0) + : shared_count(shared) + , weak_count(weak) + {} + }; + template struct shared_ptr; + /** + * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by + * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created + * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular + * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the + * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the + * weak_ptr becomes expired and cannot be used to access the object anymore. + */ + template + struct weak_ptr + { + template + friend struct shared_ptr; + + /** + * @brief Constructs a null weak_ptr. + */ + weak_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + + template + requires(std::is_convertible_v) + weak_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. + */ + weak_ptr(weak_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. + */ + weak_ptr(weak_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. + */ + auto operator=(weak_ptr const & other) -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + if (control != nullptr) + { + ++(control->weak_count); + } + } + + return *this; + } + + /** + * @brief Move assignment operator. Move-assigns a weak_ptr from other. + */ + auto operator=(weak_ptr && other) noexcept -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + } + + return *this; + } + + /** + * @brief Destructor. Cleans up resources if necessary. + */ + ~weak_ptr() + { + cleanup(); + } + + /** + * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an + * empty shared_ptr otherwise. + */ + [[nodiscard]] auto lock() const -> shared_ptr + { + return shared_ptr(*this); + } + + private: + auto cleanup() -> void + { + if (control != nullptr) + { + if (--(control->weak_count) == 0 && control->shared_count == 0) + { + delete control; + } + } + } + + T * pointer; + shared_control_block * control; + }; + + /** + * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to + * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to + * create shared_ptr instances in a member function of the object. + * + * @tparam T The type of the managed object. + */ + template + struct enable_shared_from_this + { + template + friend struct shared_ptr; + + friend T; + + public: + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() -> shared_ptr + { + return shared_ptr(weak_this); + } + + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() const -> shared_ptr + { + return shared_ptr(weak_this); + } + + private: + enable_shared_from_this() = default; + enable_shared_from_this(enable_shared_from_this const &) = default; + auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default; + ~enable_shared_from_this() = default; + + void internal_assign_ptr(shared_ptr const & ptr) const + { + weak_this = ptr; + } + + mutable weak_ptr weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality. + }; + /** * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of @@ -31,12 +221,23 @@ namespace kstd template friend struct shared_ptr; + template + friend struct weak_ptr; + + /** + * @brief Construct an empty shared_ptr. + */ + shared_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + /** * @brief Construct an empty shared_ptr from nullptr. */ shared_ptr(std::nullptr_t) noexcept : pointer(nullptr) - , ref_count(nullptr) + , control(nullptr) {} /** @@ -44,11 +245,33 @@ namespace kstd * * @param pointer A pointer to an object to manage (default is nullptr). */ - explicit shared_ptr(T * pointer = nullptr) + template + requires(std::is_convertible_v) + explicit shared_ptr(U * pointer = nullptr) : pointer(pointer) - , ref_count(pointer != nullptr ? new std::atomic(1) : nullptr) + , control(pointer != nullptr ? new shared_control_block() : nullptr) + { + assign_enable_shared_from_this(pointer); + } + + /** + * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares + * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr. + * + * @param other The weak_ptr to construct from. + */ + template + requires(std::is_convertible_v) + explicit shared_ptr(weak_ptr const & other) + : pointer(nullptr) + , control(nullptr) { - // Nothing to do. + if (other.control != nullptr && other.control->shared_count != 0) + { + pointer = other.pointer; + control = other.control; + ++(control->shared_count); + } } /** @@ -58,11 +281,11 @@ namespace kstd */ shared_ptr(shared_ptr const & other) : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -76,11 +299,11 @@ namespace kstd requires(std::is_convertible_v) shared_ptr(shared_ptr const & other) : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -91,10 +314,10 @@ namespace kstd */ shared_ptr(shared_ptr && other) noexcept : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } /** @@ -107,10 +330,10 @@ namespace kstd requires(std::is_convertible_v) shared_ptr(shared_ptr && other) noexcept : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } /** @@ -127,11 +350,11 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -151,11 +374,11 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } return *this; @@ -174,9 +397,9 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } return *this; @@ -195,9 +418,9 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; return *this; } @@ -209,7 +432,7 @@ namespace kstd { cleanup(); pointer = nullptr; - ref_count = nullptr; + control = nullptr; return *this; } @@ -230,7 +453,8 @@ namespace kstd { cleanup(); pointer = ptr; - ref_count = ptr != nullptr ? new std::atomic(1) : nullptr; + control = ptr != nullptr ? new shared_control_block() : nullptr; + assign_enable_shared_from_this(ptr); } /** @@ -242,7 +466,7 @@ namespace kstd void swap(shared_ptr & other) { std::swap(pointer, other.pointer); - std::swap(ref_count, other.ref_count); + std::swap(control, other.control); } /** @@ -288,9 +512,9 @@ namespace kstd */ [[nodiscard]] auto use_count() const -> std::size_t { - if (ref_count != nullptr) + if (control != nullptr) { - return *ref_count; + return control->shared_count; } return 0; @@ -328,20 +552,48 @@ namespace kstd [[nodiscard]] auto operator<=>(shared_ptr const & other) const = default; private: + /** + * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this + * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the + * candidate type does not inherit from enable_shared_from_this, this function does nothing. + * + * @tparam U The candidate type to check for enable_shared_from_this inheritance. + * @param candidate The candidate object to assign the internal weak pointer for. + */ + template + auto assign_enable_shared_from_this(U * candidate) -> void + { + if constexpr (requires(U * p, shared_ptr const & sp) { p->internal_assign_ptr(sp); }) + { + if (candidate != nullptr) + { + candidate->internal_assign_ptr(*this); + } + } + } + /** * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object. */ auto cleanup() -> void { - if (ref_count != nullptr && --(*ref_count) == 0) + if (control != nullptr) { - delete pointer; - delete ref_count; + if (--(control->shared_count) == 0) + { + delete pointer; + pointer = nullptr; + + if (control->weak_count == 0) + { + delete control; + } + } } } - T * pointer; ///< The managed object. - std::atomic * ref_count; ///< Reference count. + T * pointer; ///< The managed object. + shared_control_block * control; ///< Shared control block. }; /** -- cgit v1.2.3 From 10b77e5c9741211f99cefecd50bcec76dc046d84 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 19:07:02 +0100 Subject: implement simple kstd::string --- libs/kstd/include/kstd/string | 264 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 libs/kstd/include/kstd/string (limited to 'libs') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string new file mode 100644 index 0000000..62126a9 --- /dev/null +++ b/libs/kstd/include/kstd/string @@ -0,0 +1,264 @@ +#ifndef KSTD_STRING_HPP +#define KSTD_STRING_HPP + +#include +#include +#include + +#include +#include +#include + +namespace kstd +{ + /** + * @brief A simple string implementation that owns its data and provides basic operations. + */ + struct string + { + //! The type of the characters contained in this string. + using value_type = char; + //! The type of the underlying storage used by this string. + using storage_type = kstd::vector; + //! The type of all sizes used in and with this string. + 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 single values in this string. + using reference = value_type &; + //! The type of references to constant single values in this string. + using const_reference = value_type const &; + //! The type of pointers to single values in this string. + using pointer = value_type *; + //! The type of pointers to constant single values in this string. + using const_pointer = value_type const *; + //! The type of iterators into this string. + using iterator = pointer; + //! The type of constant iterators into this string. + using const_iterator = const_pointer; + + /** + * @brief Constructs an empty null-terminated string. + */ + string() + : m_storage{value_type{'\0'}} + {} + + /** + * @brief Constructs a string from a string view by copying the characters into owned storage. + * @param view The string view to copy the characters from. + */ + string(std::string_view view) + : string() + { + append(view); + } + + /** + * @brief Constructs a string by copying another string. + * @param other The string to copy. + */ + constexpr string(string const & other) + : m_storage{other.m_storage} + {} + + /** + * @brief Destructs the string. + */ + constexpr ~string() = default; + + /** + * @brief Assigns the value of another string to this string. + * @param other The string to assign from. + * @return A reference to this string. + */ + constexpr auto operator=(string const & other) -> string & = default; + + /** + * @brief Returns the number of characters in this string, not including the null terminator. + */ + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_storage.empty() ? 0 : m_storage.size() - 1; + } + + /** + * @brief Checks if this string is empty, not including the null terminator. + */ + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return size() == 0; + } + + /** + * @brief Clears the content of the string, resulting in an empty string. + * The string remains null-terminated after this operation. + */ + constexpr auto clear() -> void + { + m_storage.clear(); + m_storage.push_back(value_type{'\0'}); + } + + //! Get a pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer + { + return data(); + } + + //! Get an iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + //! Get an iterator to the end of the string + [[nodiscard]] constexpr auto end() noexcept -> iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator + { + return end(); + } + + //! Get a reference to the first character of the string + [[nodiscard]] constexpr auto front() -> reference + { + return m_storage.front(); + } + + //! Get a const reference to the first character of the string + [[nodiscard]] constexpr auto front() const -> const_reference + { + return m_storage.front(); + } + + //! Get a reference to the last character of the string + [[nodiscard]] constexpr auto back() -> reference + { + return m_storage[size() - 1]; + } + + //! Get a const reference to the last character of the string + [[nodiscard]] constexpr auto back() const -> const_reference + { + return m_storage[size() - 1]; + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + */ + constexpr auto push_back(value_type ch) -> void + { + m_storage.back() = ch; + m_storage.push_back(value_type{'\0'}); + } + + /** + * @brief Appends a string view to the end of the string by copying the characters into owned storage. + * @param view The string view to append. + * @return A reference to this string. + */ + constexpr auto append(std::string_view view) -> string & + { + if (!view.empty()) + { + std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); + } + + return *this; + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto append(string const & other) -> string & + { + return append(other.view()); + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto operator+=(string const & other) -> string & + { + return append(other); + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + * @return A reference to this string. + */ + constexpr auto operator+=(value_type ch) -> string & + { + push_back(ch); + return *this; + } + + /** + * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. + */ + [[nodiscard]] constexpr auto view() const noexcept -> std::string_view + { + return std::string_view{data(), size()}; + } + + private: + //! The underlying storage of the string, which owns the characters and ensures null-termination. + storage_type m_storage{}; + }; + + /** + * @brief Concatenates a strings and a character and returns the result as a new string. + * @param lhs The string to concatenate. + * @param c The character to concatenate. + * @return A new string that is the result of concatenating @p lhs and @p c. + */ + [[nodiscard]] constexpr auto inline operator+(string const & lhs, char const c) -> string + { + string result{lhs}; + result += c; + return result; + } +} // namespace kstd + +#endif \ No newline at end of file -- cgit v1.2.3 From c470ca76ce7801a2a4efb03c9ed606b34b368ded Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 23:57:21 +0100 Subject: implement simple conversion function from unsigned integral values to kstd::string --- libs/kstd/include/kstd/string | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 62126a9..f9583d5 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -54,6 +55,29 @@ namespace kstd append(view); } + /** + * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. + * @param c_str The null-terminated C-style string to copy. + */ + string(char const * c_str) + : string() + { + if (c_str != nullptr) + { + append(std::string_view{c_str}); + } + } + + /** + * @brief Constructs a string containing a single character. + * @param c The character to copy. + */ + string(value_type c) + : string() + { + push_back(c); + } + /** * @brief Constructs a string by copying another string. * @param other The string to copy. @@ -250,13 +274,42 @@ namespace kstd /** * @brief Concatenates a strings and a character and returns the result as a new string. * @param lhs The string to concatenate. - * @param c The character to concatenate. - * @return A new string that is the result of concatenating @p lhs and @p c. + * @param rhs The string to concatenate. + * @return A new string that is the result of concatenating @p lhs and @p rhs. */ - [[nodiscard]] constexpr auto inline operator+(string const & lhs, char const c) -> string + [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string { string result{lhs}; - result += c; + result += rhs; + return result; + } + + /** + * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and + * concatenating them. + * @tparam N 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 + requires std::unsigned_integral + [[nodiscard]] constexpr auto inline to_string(N value) -> string + { + if (value == 0) + { + return "0"; + } + + string result; + + while (value > 0) + { + char const digit = '0' + (value % 10); + result.push_back(digit); + value /= 10; + } + + std::reverse(result.begin(), result.end()); return result; } } // namespace kstd -- cgit v1.2.3 From 336b25458b75e28c93c0bab23ccd359042f9df41 Mon Sep 17 00:00:00 2001 From: "marcel.braun" Date: Mon, 23 Mar 2026 21:40:34 +0100 Subject: Implement == and != operators for string and string_view --- libs/kstd/include/kstd/string | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index f9583d5..075422e 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -312,6 +312,37 @@ namespace kstd std::reverse(result.begin(), result.end()); return result; } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool + { + return lhs.view() == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool + { + return lhs.view() == rhs; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool + { + return lhs == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + } // namespace kstd #endif \ No newline at end of file -- cgit v1.2.3 From 2eb086d516f20a0b5cef9881a3459adb389c6ee8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 24 Mar 2026 19:06:50 +0100 Subject: implement == and <=> operator in shared_ptr --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index 674807d..8930095 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -546,11 +546,6 @@ namespace kstd return ptr.pointer == nullptr; } - /** - * @brief Defaulted three-way comparator operator. - */ - [[nodiscard]] auto operator<=>(shared_ptr const & other) const = default; - private: /** * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this @@ -624,6 +619,30 @@ namespace kstd { return shared_ptr(new T(std::forward(args)...)); } + + /** + * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return true if lhs and rhs point to the same object, false otherwise. + */ + template + [[nodiscard]] auto inline operator==(shared_ptr const & lhs, shared_ptr const & rhs) -> bool + { + return lhs.get() == rhs.get(); + } + + /** + * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>. + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return The result of comparing the stored pointers of lhs and rhs using operator<=> + */ + template + [[nodiscard]] auto inline operator<=>(shared_ptr const & lhs, shared_ptr const & rhs) + { + return lhs.get() <=> rhs.get(); + } } // namespace kstd #endif \ No newline at end of file -- cgit v1.2.3 From 4b094e2bd5a8e60ec1018d6aa90aa9da4adf49c9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 26 Mar 2026 18:04:12 +0100 Subject: kstd/vector: implement single-element erase --- libs/kstd/include/kstd/vector | 16 ++++++++ libs/kstd/tests/src/vector.cpp | 91 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 0d4aac8..74aefa9 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -690,6 +690,22 @@ namespace kstd return begin() + prefix_size; } + 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; + } + //! Append a given element to this vector via copy construction. constexpr auto push_back(value_type const & value) -> void { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 0735c9a..3ff041f 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -649,6 +649,45 @@ SCENARIO("Vector modifiers", "[vector]") 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()); + } + } } } @@ -868,6 +907,58 @@ SCENARIO("Vector modifier move semantics", "[vector]") REQUIRE(v[3].was_moved); } } + + WHEN("erasing an element in the middle") + { + for (auto& elem : v) + { + elem.was_copied = false; + elem.was_moved = false; + } + + 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_FALSE(v[0].was_moved); + REQUIRE_FALSE(v[0].was_copied); + + REQUIRE(v[1].value == 30); + REQUIRE(v[1].was_moved); + REQUIRE_FALSE(v[1].was_copied); + + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + for (auto& elem : v) + { + elem.was_copied = false; + elem.was_moved = false; + } + + 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_FALSE(v[0].was_moved); + REQUIRE_FALSE(v[0].was_copied); + + REQUIRE(v[1].value == 20); + REQUIRE_FALSE(v[1].was_moved); + REQUIRE_FALSE(v[1].was_copied); + + REQUIRE(it == v.end()); + } + } } } -- cgit v1.2.3 From f6100699cd93606147ebe94a777b6e4aff7c5f50 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 26 Mar 2026 18:09:15 +0100 Subject: kstd/vector: add missing tests for insert --- libs/kstd/tests/src/vector.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'libs') diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 3ff041f..02b8786 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -650,6 +650,40 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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()); -- cgit v1.2.3 From 096d7505cfc2d60e58a6dd4d80fd7f3638c9bb94 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 26 Mar 2026 18:19:52 +0100 Subject: kstd/vector: increase test coverage --- libs/kstd/include/kstd/vector | 7 ++++++- libs/kstd/tests/src/vector.cpp | 44 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 74aefa9..771fc87 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -548,7 +548,6 @@ namespace kstd if (new_capacity > max_size()) { kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible."); - return; } auto new_data = allocate_n(new_capacity); @@ -690,6 +689,12 @@ namespace kstd 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()) diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 02b8786..913427c 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -475,6 +475,20 @@ SCENARIO("Vector modifiers", "[vector]") REQUIRE(it == v.begin()); } } + + WHEN("inserting an lvalue element") + { + auto const value = 42; + 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] == 42); + REQUIRE(it == v.begin()); + } + } } GIVEN("A populated vector") @@ -597,6 +611,22 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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); @@ -722,6 +752,14 @@ SCENARIO("Vector modifiers", "[vector]") 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); + } + } } } @@ -944,7 +982,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") WHEN("erasing an element in the middle") { - for (auto& elem : v) + for (auto & elem : v) { elem.was_copied = false; elem.was_moved = false; @@ -955,7 +993,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") THEN("the subsequent elements are move-assigned leftwards") { REQUIRE(v.size() == 2); - + REQUIRE(v[0].value == 10); REQUIRE_FALSE(v[0].was_moved); REQUIRE_FALSE(v[0].was_copied); @@ -970,7 +1008,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") WHEN("erasing the last element") { - for (auto& elem : v) + for (auto & elem : v) { elem.was_copied = false; elem.was_moved = false; -- cgit v1.2.3 From 11c6d57e013832983bcd9bb965d470bf4c282ab6 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 26 Mar 2026 18:40:39 +0100 Subject: kstd/vector: implement range erase --- libs/kstd/include/kstd/vector | 22 ++++++++++++++++ libs/kstd/tests/src/vector.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 771fc87..9e41cb6 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -711,6 +711,28 @@ namespace kstd 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 { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 913427c..b7971f4 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -760,6 +760,33 @@ SCENARIO("Vector modifiers", "[vector]") 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); + } + } } } @@ -1031,6 +1058,39 @@ SCENARIO("Vector modifier move semantics", "[vector]") 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.was_copied = false; + elem.was_moved = false; + } + + 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_FALSE(v[0].was_moved); + REQUIRE_FALSE(v[0].was_copied); + + REQUIRE(v[1].value == 40); + REQUIRE(v[1].was_moved); + REQUIRE_FALSE(v[1].was_copied); + + REQUIRE(v[2].value == 50); + REQUIRE(v[2].was_moved); + REQUIRE_FALSE(v[2].was_copied); + + REQUIRE(it == v.begin() + 1); + } + } } } -- cgit v1.2.3 From 3070bb45b9741165d786b2c5a018ee55c1a82db8 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 27 Mar 2026 07:05:05 +0100 Subject: kernel/interrupts: switch to flat_map for dispatch --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/bits/flat_map.hpp | 82 ++++++++ libs/kstd/include/kstd/flat_map | 320 +++++++++++++++++++++++++++++++ libs/kstd/tests/src/flat_map.cpp | 288 ++++++++++++++++++++++++++++ 4 files changed, 691 insertions(+) create mode 100644 libs/kstd/include/kstd/bits/flat_map.hpp create mode 100644 libs/kstd/include/kstd/flat_map create mode 100644 libs/kstd/tests/src/flat_map.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 06543ab..ff3c8cc 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -44,6 +44,7 @@ endif() if(NOT CMAKE_CROSSCOMPILING) add_executable("kstd_tests" + "tests/src/flat_map.cpp" "tests/src/vector.cpp" "tests/src/os_panic.cpp" ) diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp new file mode 100644 index 0000000..903841e --- /dev/null +++ b/libs/kstd/include/kstd/bits/flat_map.hpp @@ -0,0 +1,82 @@ +#ifndef KSTD_BITS_FLAT_MAP_HPP +#define KSTD_BITS_FLAT_MAP_HPP + +#include +#include +#include + +namespace kstd::bits +{ + + template + struct flat_map_iterator + { + using iterator_category = std::random_access_iterator_tag; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using reference = std::pair; + using pointer = void; + + constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) + : m_key_iterator{key_iterator} + , m_mapped_iterator{mapped_iterator} + {} + + [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator + { + return m_key_iterator; + } + + [[nodiscard]] constexpr auto operator*() const noexcept -> reference + { + return {*m_key_iterator, *m_mapped_iterator}; + } + + constexpr auto operator++() noexcept -> flat_map_iterator & + { + ++m_key_iterator; + ++m_mapped_iterator; + return *this; + } + + constexpr auto operator++(int) noexcept -> flat_map_iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + constexpr auto operator--() noexcept -> flat_map_iterator & + { + --m_key_iterator; + --m_mapped_iterator; + return *this; + } + + constexpr auto operator--(int) noexcept -> flat_map_iterator + { + auto copy = *this; + --(*this); + return copy; + } + + [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator + { + return {m_key_iterator + offset, m_mapped_iterator + offset}; + } + + [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type + { + return m_key_iterator - other.m_key_iterator; + } + + [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default; + + private: + KeyIterator m_key_iterator{}; + MappedIterator m_mapped_iterator{}; + }; + +} // namespace kstd::bits + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map new file mode 100644 index 0000000..6ffbbcf --- /dev/null +++ b/libs/kstd/include/kstd/flat_map @@ -0,0 +1,320 @@ +#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 + { + using key_container_type = KeyContainerType; + using mapped_container_type = MappedContainerType; + using key_type = KeyType; + using mapped_type = MappedType; + using value_type = std::pair; + 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} + {} + + //! 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()}; + } + + //! 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 + }; + } + + //! 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(); + } + + private: + containers m_containers; + key_compare m_comparator; + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/tests/src/flat_map.cpp b/libs/kstd/tests/src/flat_map.cpp new file mode 100644 index 0000000..cde136a --- /dev/null +++ b/libs/kstd/tests/src/flat_map.cpp @@ -0,0 +1,288 @@ +#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)); + } + } + } +} + +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)); + } + } + } +} + +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()); + } + } + + 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); + } + } + } +} -- cgit v1.2.3 From 610707e896504a33fa82db4905e57a4822d3bb9d Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Fri, 27 Mar 2026 19:39:55 +0100 Subject: add string tests --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/tests/src/string.cpp | 390 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 libs/kstd/tests/src/string.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 06543ab..d4f415f 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -46,6 +46,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_executable("kstd_tests" "tests/src/vector.cpp" "tests/src/os_panic.cpp" + "tests/src/string.cpp" ) target_include_directories("kstd_tests" PRIVATE diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp new file mode 100644 index 0000000..f7f629d --- /dev/null +++ b/libs/kstd/tests/src/string.cpp @@ -0,0 +1,390 @@ +#include + +#include + +#include +#include +#include + +SCENARIO("String initialization and construction", "[string]") +{ + GIVEN("Nothing") + { + WHEN("constructing an empty string") + { + auto str = kstd::string{}; + + THEN("the size is zero and therefore the string is empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string is empty") + { + REQUIRE(str.view() == std::string_view{}); + } + } + } + + GIVEN("A string view") + { + auto view = std::string_view{"Blub Blub"}; + + WHEN("constructing a string from string_view") + { + auto str = kstd::string{view}; + + THEN("the string is not empty and has the same size as the view") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == view.size()); + } + + THEN("the string contains the same characters as the view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A C-style string") + { + auto c_str = "Blub Blub"; + + WHEN("constructing a string from the C-style string") + { + auto str = kstd::string{c_str}; + + THEN("the string is not empty and has the same size as the C-style string") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == strlen(c_str)); + } + + THEN("the string contains the same characters as the C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } + + GIVEN("A character") + { + auto ch = 'x'; + + WHEN("constructing a string from the character") + { + auto str = kstd::string{ch}; + + THEN("the string is not empty and has size 1") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == 1); + } + + THEN("the string contains the same character as the given character") + { + REQUIRE(str.view() == std::string_view{&ch, 1}); + } + } + } + + GIVEN("Another string") + { + auto other = kstd::string{"Blub Blub"}; + + WHEN("copy constructing a new string") + { + auto str = kstd::string{other}; + + THEN("the new string contains the same characters as the original") + { + REQUIRE(str.view() == other.view()); + } + } + + auto str = kstd::string{"Blub"}; + + WHEN("copy assigning another string") + { + auto other = kstd::string{"Blub Blub"}; + str = other; + + THEN("the string contains the same characters as the assigned string") + { + REQUIRE(str.view() == other.view()); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a string view") + { + auto view = std::string_view{"Hello, world!"}; + str = view; + + THEN("the string contains the same characters as the assigned view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a C-style string") + { + auto c_str = "Hello, world!"; + str = c_str; + + THEN("the string contains the same characters as the assigned C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } +} + +SCENARIO("String concatenation", "[string]") +{ + 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.view() == "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.view() == "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 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.view() == "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()); + } + } + } + + 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.view() == "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.view() == "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.view() == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + } +} + +SCENARIO("String conversion and comparison", "[string]") +{ + GIVEN("An unsigned integer") + { + auto value1 = 12345u; + 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.view() == "12345"); + REQUIRE(str2.view() == "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(!(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(!(str != view)); + REQUIRE(!(view != str)); + } + } +} + +SCENARIO("String clearing", "[string]") +{ + 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.view() == std::string_view{}); + } + } + } +} + +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 : str.view()) + { + 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'); + } + } + } +} -- cgit v1.2.3 From 4f7ae11655807acf68f49637cc9dd01a03af36d5 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Fri, 27 Mar 2026 20:31:48 +0100 Subject: add some more tests --- libs/kstd/tests/src/string.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'libs') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index f7f629d..43e9a6b 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -387,4 +387,59 @@ SCENARIO("String iteration", "[string]") } } } + + 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 : str.view()) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str.view()); + } + } + + 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 : str.view()) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(result.view() == std::string_view{}); + } + } + } } -- cgit v1.2.3 From 1f0d290bc303ac8f039963c4eb6421536d36827c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 28 Mar 2026 17:29:31 +0100 Subject: string tests --- libs/kstd/tests/src/string.cpp | 84 ++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 35 deletions(-) (limited to 'libs') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index 43e9a6b..a94f0f6 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -386,60 +386,74 @@ SCENARIO("String iteration", "[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") + GIVEN("A non-empty string") { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } + auto str = kstd::string{"Hello"}; - THEN("the iterated characters are the same as the characters in the string") + WHEN("using const end()") { - REQUIRE(result == str.view()); + auto it = str.end(); // calls const end() + THEN("it points past the last character") + { + REQUIRE(*std::prev(it) == 'o'); + REQUIRE(std::distance(str.begin(), it) == str.size()); + } } } - WHEN("using front and back to access the first and last characters of the string") + GIVEN("A const string") { - THEN("front returns the first character of the string") + auto const str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") { - REQUIRE(str.front() == 'B'); + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str.view()); + } } - THEN("back returns the last character of the string") + WHEN("using front and back to access the first and last characters of the string") { - REQUIRE(str.back() == 'b'); + 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") + GIVEN("An empty string") { - kstd::string result; + auto str = kstd::string{}; - for (auto ch : str.view()) + WHEN("iterating over the characters of an empty string") { - result.push_back(ch); - } + kstd::string result; - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(result.view() == std::string_view{}); + for (auto ch : str.view()) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(result.view() == std::string_view{}); + } } } } -} -- cgit v1.2.3 From ddfa0cd69ec06b2ccaae36cb8dd676a324742b9c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 28 Mar 2026 17:37:17 +0100 Subject: Revert "string tests" This reverts commit 1f0d290bc303ac8f039963c4eb6421536d36827c. --- libs/kstd/tests/src/string.cpp | 84 ++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 49 deletions(-) (limited to 'libs') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index a94f0f6..43e9a6b 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -386,74 +386,60 @@ SCENARIO("String iteration", "[string]") REQUIRE(str.back() == 'b'); } } + } + + GIVEN("A const string") + { + auto const str = kstd::string{"Blub"}; - GIVEN("A non-empty string") + WHEN("iterating over the characters of the string as string_view using a range-based for loop") { - auto str = kstd::string{"Hello"}; + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } - WHEN("using const end()") + THEN("the iterated characters are the same as the characters in the string") { - auto it = str.end(); // calls const end() - THEN("it points past the last character") - { - REQUIRE(*std::prev(it) == 'o'); - REQUIRE(std::distance(str.begin(), it) == str.size()); - } + REQUIRE(result == str.view()); } } - GIVEN("A const string") + WHEN("using front and back to access the first and last characters of the string") { - auto const str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") + THEN("front returns the first character of the string") { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str.view()); - } + REQUIRE(str.front() == 'B'); } - WHEN("using front and back to access the first and last characters of the string") + THEN("back returns the last character 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'); - } + REQUIRE(str.back() == 'b'); } } + } + + GIVEN("An empty string") + { + auto str = kstd::string{}; - GIVEN("An empty string") + WHEN("iterating over the characters of an empty string") { - auto str = kstd::string{}; + kstd::string result; - WHEN("iterating over the characters of an empty string") + for (auto ch : str.view()) { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } + result.push_back(ch); + } - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(result.view() == std::string_view{}); - } + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(result.view() == std::string_view{}); } } } +} -- cgit v1.2.3 From c946cf6a89bbeae7fb96a67b55d91b7ae0cfa48d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 30 Mar 2026 12:41:14 +0000 Subject: kstd/flat_map: fix iterator reference --- libs/kstd/include/kstd/bits/flat_map.hpp | 108 ++++++++++++++++++++++++++++++- libs/kstd/tests/src/flat_map.cpp | 25 +++++++ 2 files changed, 131 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp index 903841e..9455549 100644 --- a/libs/kstd/include/kstd/bits/flat_map.hpp +++ b/libs/kstd/include/kstd/bits/flat_map.hpp @@ -1,27 +1,107 @@ #ifndef KSTD_BITS_FLAT_MAP_HPP #define KSTD_BITS_FLAT_MAP_HPP +#include #include #include +#include +#include +#include #include namespace kstd::bits { + template + struct flat_map_reference + { + using key_type = KeyType; + using mapped_type = MappedType; + + constexpr flat_map_reference(key_type const & key, mapped_type & mapped) + : first{key} + , second{mapped} + {} + + constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const & + { + second = other.second; + return *this; + } + + constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const & + { + second = std::move(other.second); + return *this; + } + + template + requires(std::tuple_size_v> == 2) + constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const & + { + second = std::forward(tuple).second; + return *this; + } + + template + requires(Index >= 0 && Index <= 1) + constexpr auto get() const noexcept -> decltype(auto) + { + if constexpr (Index == 0) + { + return (first); + } + else + { + return (second); + } + } + + key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + }; + + template + struct flat_map_pointer + { + Reference reference; + + [[nodiscard]] constexpr auto operator->() noexcept -> Reference * + { + return std::addressof(reference); + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const * + { + return std::addressof(reference); + } + }; + template struct flat_map_iterator { using iterator_category = std::random_access_iterator_tag; using value_type = std::pair; using difference_type = std::ptrdiff_t; - using reference = std::pair; - using pointer = void; + using reference = flat_map_reference; + using pointer = flat_map_pointer; + + constexpr flat_map_iterator() = default; constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) : m_key_iterator{key_iterator} , m_mapped_iterator{mapped_iterator} {} + template + requires(std::convertible_to && + std::convertible_to) + constexpr flat_map_iterator( + flat_map_iterator const & other) noexcept + : m_key_iterator{other.m_key_iterator} + , m_mapped_iterator{other.m_mapped_iterator} + {} + [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator { return m_key_iterator; @@ -32,6 +112,13 @@ namespace kstd::bits return {*m_key_iterator, *m_mapped_iterator}; } + [[nodiscard]] constexpr auto operator->() const noexcept -> pointer + { + return { + {*m_key_iterator, *m_mapped_iterator} + }; + } + constexpr auto operator++() noexcept -> flat_map_iterator & { ++m_key_iterator; @@ -79,4 +166,21 @@ namespace kstd::bits } // namespace kstd::bits +template +struct std::tuple_size> : std::integral_constant +{ +}; + +template +struct std::tuple_element<0, kstd::bits::flat_map_reference> +{ + using type = K const &; +}; + +template +struct std::tuple_element<1, kstd::bits::flat_map_reference> +{ + using type = M &; +}; + #endif \ No newline at end of file diff --git a/libs/kstd/tests/src/flat_map.cpp b/libs/kstd/tests/src/flat_map.cpp index cde136a..eb599af 100644 --- a/libs/kstd/tests/src/flat_map.cpp +++ b/libs/kstd/tests/src/flat_map.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include SCENARIO("Flat Map initialization and construction", "[flat_map]") { @@ -159,6 +161,29 @@ SCENARIO("Flat Map iterators", "[flat_map]") ++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") -- cgit v1.2.3 From 5ae03c52fe33882416aa6044993d8422ccb33ab4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 1 Apr 2026 08:49:21 +0200 Subject: kernel: begin basic bht implementation --- libs/kstd/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 7169aa8..ec0f441 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -40,9 +40,7 @@ if(CMAKE_CROSSCOMPILING) list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=") target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) -endif() - -if(NOT CMAKE_CROSSCOMPILING) +else() add_executable("kstd_tests" "tests/src/flat_map.cpp" "tests/src/vector.cpp" -- cgit v1.2.3 From 38bdee2ba829999862e37999dc212055ebedc4c6 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 1 Apr 2026 15:52:43 +0200 Subject: kstd: fix signatures of libc functions --- libs/kstd/include/kstd/cstring | 10 +++++----- libs/kstd/src/libc/string.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring index a5c41fa..bd8b28d 100644 --- a/libs/kstd/include/kstd/cstring +++ b/libs/kstd/include/kstd/cstring @@ -8,12 +8,12 @@ namespace kstd::libc extern "C" { - auto memcpy(void * dest, void const * src, std::size_t size) -> void *; - auto memset(void * dest, int value, std::size_t size) -> void *; - auto memmove(void * dest, void const * src, std::size_t size) -> void *; - auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t; + 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) -> std::size_t; + auto strlen(char const * string) noexcept -> std::size_t; } } // namespace kstd::libc diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp index 63f012c..c9fada1 100644 --- a/libs/kstd/src/libc/string.cpp +++ b/libs/kstd/src/libc/string.cpp @@ -9,7 +9,7 @@ namespace kstd::libc { - auto memcpy(void * dest, void const * src, std::size_t size) -> void * + auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void * { auto dest_span = std::span{static_cast(dest), size}; auto src_span = std::span{static_cast(src), size}; @@ -22,7 +22,7 @@ namespace kstd::libc return dest; } - auto memset(void * dest, int value, std::size_t size) -> void * + auto memset(void * dest, int value, std::size_t size) noexcept -> void * { auto const byte_value = static_cast(static_cast(value)); auto dest_span = std::span{static_cast(dest), size}; @@ -35,7 +35,7 @@ namespace kstd::libc return dest; } - auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t + auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int { auto left_span = std::span{static_cast(lhs), size}; auto right_span = std::span{static_cast(rhs), size}; @@ -49,7 +49,7 @@ namespace kstd::libc return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); } - auto memmove(void * dest, void const * src, std::size_t size) -> void * + auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void * { auto dest_span = std::span{static_cast(dest), size}; auto src_span = std::span{static_cast(src), size}; @@ -70,7 +70,7 @@ namespace kstd::libc return dest; } - auto strlen(char const * string) -> std::size_t + auto strlen(char const * string) noexcept -> std::size_t { return std::distance(string, std::ranges::find(string, nullptr, '\0')); } -- cgit v1.2.3 From 77473afe9d5acb9450443b07b56d3dbc2f0639a6 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 31 Mar 2026 22:22:25 +0200 Subject: kstd: introduce observer_ptr --- libs/kstd/include/kstd/observer_ptr | 154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 libs/kstd/include/kstd/observer_ptr (limited to 'libs') diff --git a/libs/kstd/include/kstd/observer_ptr b/libs/kstd/include/kstd/observer_ptr new file mode 100644 index 0000000..d3d24b4 --- /dev/null +++ b/libs/kstd/include/kstd/observer_ptr @@ -0,0 +1,154 @@ +#ifndef KSTD_OBSERVER_PTR_HPP +#define KSTD_OBSERVER_PTR_HPP + +#include "kstd/os/error.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + + template + struct observer_ptr + { + //! The type of the element being pointed to. + using element_type = ElementType; + + //! Construct an empty observer pointer. + constexpr observer_ptr() noexcept = default; + + //! Construct an empty observer pointer from a null pointer. + constexpr observer_ptr(std::nullptr_t) noexcept {} + + //! Construct an observer pointer from a raw pointer. + constexpr explicit observer_ptr(element_type * pointer) + : m_ptr{pointer} + {} + + //! Construct an observer pointer from another observer pointer. + template + requires std::convertible_to + constexpr observer_ptr(observer_ptr other) noexcept + : m_ptr{other.get()} + {} + + //! Copy construct an observer pointer. + constexpr observer_ptr(observer_ptr const & other) noexcept = default; + + //! Move construct an observer pointer. + constexpr observer_ptr(observer_ptr && other) noexcept = default; + + //! Stop watching the the watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto release() noexcept -> element_type * + { + return std::exchange(m_ptr, nullptr); + } + + //! Reset the observer pointer. + //! + //! @param pointer The new object to watch. + constexpr auto reset(element_type * pointer) noexcept -> void + { + m_ptr = pointer; + } + + //! Swap the observer pointer with another observer pointer. + //! + //! @param other The other observer pointer to swap with. + constexpr auto swap(observer_ptr & other) noexcept -> void + { + std::swap(m_ptr, other.m_ptr); + } + + //! Get the currently watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto get() const noexcept -> element_type * + { + return m_ptr; + } + + //! Check if the observer pointer is watching an object. + //! + //! @return True if the observer pointer is watching an object, false otherwise. + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_ptr != nullptr; + } + + //! Get the currently watched object. + //! + //! @return A reference to the currently watched object. + [[nodiscard]] constexpr auto operator*() const noexcept -> element_type & + { + throw_on_null(); + return *m_ptr; + } + + //! Get the currently watched object. + //! + //! @return A pointer to the currently watched object. + [[nodiscard]] constexpr auto operator->() const noexcept -> element_type * + { + throw_on_null(); + return m_ptr; + } + + //! Convert the observer pointer to a raw pointer. + //! + //! @return A pointer to the currently watched object. + constexpr explicit operator element_type *() const noexcept + { + return m_ptr; + } + + //! Compare the observer pointer with another observer pointer. + //! + //! @param other The other observer pointer to compare with. + //! @return The result of the comparison. + constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; + + private: + //! Throw an exception if the observer pointer is null. + //! + //! @throws std::runtime_error if the observer pointer is null. + constexpr auto throw_on_null() const noexcept -> void + { + if (m_ptr == nullptr) + { + os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); + } + } + + //! The raw pointer to the watched object. + ElementType * m_ptr; + }; + + //! Swap two observer pointers. + //! + //! @param lhs The first observer pointer to swap. + //! @param rhs The second observer pointer to swap. + template + constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void + { + lhs.swap(rhs); + } + + //! Create an observer pointer from a raw pointer. + //! + //! @param pointer The raw pointer to create an observer pointer from. + //! @return An observer pointer to the given raw pointer. + template + constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr + { + return observer_ptr{pointer}; + } + +} // namespace kstd + +#endif \ No newline at end of file -- cgit v1.2.3 From 15b882d0416bb83a18e5437480c08419b2035e1f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 31 Mar 2026 22:52:52 +0200 Subject: kstd: add some basic observer_ptr tests --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/observer_ptr | 7 +- libs/kstd/tests/src/observer_ptr.cpp | 234 +++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 libs/kstd/tests/src/observer_ptr.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index ec0f441..240118e 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -44,6 +44,7 @@ else() add_executable("kstd_tests" "tests/src/flat_map.cpp" "tests/src/vector.cpp" + "tests/src/observer_ptr.cpp" "tests/src/os_panic.cpp" "tests/src/string.cpp" ) diff --git a/libs/kstd/include/kstd/observer_ptr b/libs/kstd/include/kstd/observer_ptr index d3d24b4..97c9e5e 100644 --- a/libs/kstd/include/kstd/observer_ptr +++ b/libs/kstd/include/kstd/observer_ptr @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace kstd @@ -52,7 +53,7 @@ namespace kstd //! Reset the observer pointer. //! //! @param pointer The new object to watch. - constexpr auto reset(element_type * pointer) noexcept -> void + constexpr auto reset(element_type * pointer = nullptr) noexcept -> void { m_ptr = pointer; } @@ -84,7 +85,7 @@ namespace kstd //! Get the currently watched object. //! //! @return A reference to the currently watched object. - [[nodiscard]] constexpr auto operator*() const noexcept -> element_type & + [[nodiscard]] constexpr auto operator*() const noexcept -> std::add_lvalue_reference_t { throw_on_null(); return *m_ptr; @@ -126,7 +127,7 @@ namespace kstd } //! The raw pointer to the watched object. - ElementType * m_ptr; + ElementType * m_ptr{}; }; //! Swap two observer pointers. diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp new file mode 100644 index 0000000..2fe2226 --- /dev/null +++ b/libs/kstd/tests/src/observer_ptr.cpp @@ -0,0 +1,234 @@ +#include +#include + +#include + +#include + +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") + { + struct A + { + }; + + struct B : A + { + }; + + auto value = B{}; + auto ptr = kstd::observer_ptr(&value); + auto copy = kstd::observer_ptr(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); + } + } + } +} + +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); + } + } + } +} + +SCENARIO("Observer pointer observers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + struct A + { + int value{}; + + constexpr auto operator<=>(A const & other) const noexcept = default; + }; + + auto value = A{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); + } + } + } +} \ No newline at end of file -- cgit v1.2.3 From 5f084e49a27e73fdf9ca88f46121618d9fae399f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 1 Apr 2026 06:53:41 +0200 Subject: kstd/tests: extend operation tracker --- libs/kstd/tests/include/kstd/tests/test_types.hpp | 71 ++++++++++--- libs/kstd/tests/src/vector.cpp | 119 +++++++++++++--------- 2 files changed, 128 insertions(+), 62 deletions(-) (limited to 'libs') diff --git a/libs/kstd/tests/include/kstd/tests/test_types.hpp b/libs/kstd/tests/include/kstd/tests/test_types.hpp index 6a06311..9207ee9 100644 --- a/libs/kstd/tests/include/kstd/tests/test_types.hpp +++ b/libs/kstd/tests/include/kstd/tests/test_types.hpp @@ -11,44 +11,43 @@ namespace kstd::tests //! A type tracking copy and move operations //! //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. - struct move_tracker + struct special_member_tracker { //! A value indicating that the object was moved from. constexpr auto static moved_from_v = -1; - //! A simple value to be able to track the move-from state. - int value{}; - //! A flag to track if an instance of this type was either copy constructed or copy assigned. - bool was_copied{false}; - //! A flag to track if an instance of this type was either move constructed or move assigned. - bool was_moved{false}; + constexpr special_member_tracker() + : default_constructed_count{1} + {} //! Construct a new move tracker with the given value, if any. - constexpr move_tracker(int v = 0) + constexpr special_member_tracker(int v = 0) : value{v} + , value_constructed_count{1} {} //! Construct a new move tracker by copying an existing one. - constexpr move_tracker(move_tracker const & other) + constexpr special_member_tracker(special_member_tracker const & other) : value{other.value} - , was_copied{true} + , copy_constructed_count{1} {} //! Construct a new move tracker by moving from an existing one. - constexpr move_tracker(move_tracker && other) noexcept + constexpr special_member_tracker(special_member_tracker && other) noexcept : value{other.value} - , was_moved{true} + , move_constructed_count{1} { other.value = moved_from_v; } //! Copy assign a new move tracker from an existing one. - constexpr auto operator=(move_tracker const & other) -> move_tracker & + constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker & { if (this != &other) { value = other.value; - was_copied = true; + ++copy_assigned_count; + ++other.copied_from_count; } return *this; } @@ -56,16 +55,56 @@ namespace kstd::tests //! Move assign a new move tracker from an existing one. //! //! This function ensures that the moved-from state is marked. - constexpr auto operator=(move_tracker && other) noexcept -> move_tracker & + constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker & { if (this != &other) { value = other.value; - was_moved = true; + ++move_assigned_count; other.value = moved_from_v; + ++other.moved_from_count; } return *this; } + + ~special_member_tracker() + { + ++destroyed_count; + } + + auto reset_counts() -> void + { + default_constructed_count = 0; + copy_constructed_count = 0; + move_constructed_count = 0; + value_constructed_count = 0; + copy_assigned_count = 0; + move_assigned_count = 0; + destroyed_count = 0; + copied_from_count = 0; + moved_from_count = 0; + } + + //! A simple value to be able to track the move-from state. + int value{}; + //! A counter to track how many times an instance of this type was default constructed. + std::size_t default_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy constructed. + std::size_t copy_constructed_count{0}; + //! A counter to track how many times an instance of this type was move constructed. + std::size_t move_constructed_count{0}; + //! A counter to track how many times an instance of this type was value constructed. + std::size_t value_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy assigned. + std::size_t copy_assigned_count{0}; + //! A counter to track how many times an instance of this type was move assigned. + std::size_t move_assigned_count{0}; + //! A counter to track how many times an instance of this type was destroyed. + std::size_t destroyed_count{0}; + //! A counter to track how many times an instance of this type was copied from another instance. + mutable std::size_t copied_from_count{0}; + //! A counter to track how many times an instance of this type was moved from another instance. + std::size_t moved_from_count{0}; }; //! A type that is not default constructible. diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index b7971f4..81bf32f 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -919,8 +919,8 @@ SCENARIO("Vector modifier move semantics", "[vector]") { GIVEN("An empty vector and a move tracker element") { - auto v = kstd::vector{}; - auto tracker = kstd::tests::move_tracker{42}; + auto v = kstd::vector{}; + auto tracker = kstd::tests::special_member_tracker{42}; WHEN("push_back is called with the move tracker") { @@ -929,8 +929,8 @@ SCENARIO("Vector modifier move semantics", "[vector]") THEN("the element is added and the size and capacity increase") { REQUIRE(v.size() == 1); - REQUIRE(v.back().was_moved); - REQUIRE_FALSE(v.back().was_copied); + REQUIRE(v.back().move_constructed_count == 1); + REQUIRE(v.back().copy_constructed_count == 0); REQUIRE(v.back().value == 42); } @@ -943,7 +943,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") GIVEN("An empty vector") { - auto v = kstd::vector{}; + auto v = kstd::vector{}; WHEN("emplace_back is called with constructor arguments") { @@ -952,8 +952,8 @@ SCENARIO("Vector modifier move semantics", "[vector]") THEN("the element is constructed directly, without moves or copies") { REQUIRE(v.size() == 1); - REQUIRE_FALSE(v.back().was_moved); - REQUIRE_FALSE(v.back().was_copied); + REQUIRE(v.back().move_constructed_count == 0); + REQUIRE(v.back().copy_constructed_count == 0); REQUIRE(v.back().value == 42); } } @@ -961,7 +961,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") GIVEN("A populated vector of move trackers") { - auto v = kstd::vector{}; + auto v = kstd::vector{}; v.reserve(10); v.emplace_back(10); v.emplace_back(20); @@ -969,41 +969,46 @@ SCENARIO("Vector modifier move semantics", "[vector]") WHEN("inserting an element in the middle with sufficient capacity") { - auto const tracker = kstd::tests::move_tracker{15}; + 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_FALSE(v[0].was_moved); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); REQUIRE(v[1].value == 15); - REQUIRE(v[1].was_copied); - REQUIRE_FALSE(v[1].was_moved); + 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].was_moved); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); REQUIRE(v[3].value == 30); - REQUIRE(v[3].was_moved); + REQUIRE(v[3].move_constructed_count == 1); } } WHEN("inserting an rvalue element in the middle with sufficient capacity") { - auto tracker = kstd::tests::move_tracker{15}; + 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_FALSE(v[0].was_moved); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); REQUIRE(v[1].value == 15); - REQUIRE_FALSE(v[1].was_copied); - REQUIRE(v[1].was_moved); + 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].was_moved); + REQUIRE(v[2].move_assigned_count == 1); REQUIRE(v[3].value == 30); - REQUIRE(v[3].was_moved); + REQUIRE(v[3].move_constructed_count == 1); } } @@ -1011,8 +1016,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") { for (auto & elem : v) { - elem.was_copied = false; - elem.was_moved = false; + elem.reset_counts(); } auto it = v.erase(v.cbegin() + 1); @@ -1022,12 +1026,15 @@ SCENARIO("Vector modifier move semantics", "[vector]") REQUIRE(v.size() == 2); REQUIRE(v[0].value == 10); - REQUIRE_FALSE(v[0].was_moved); - REQUIRE_FALSE(v[0].was_copied); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); REQUIRE(v[1].value == 30); - REQUIRE(v[1].was_moved); - REQUIRE_FALSE(v[1].was_copied); + 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); } @@ -1037,8 +1044,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") { for (auto & elem : v) { - elem.was_copied = false; - elem.was_moved = false; + elem.reset_counts(); } auto it = v.erase(v.cend() - 1); @@ -1048,12 +1054,20 @@ SCENARIO("Vector modifier move semantics", "[vector]") REQUIRE(v.size() == 2); REQUIRE(v[0].value == 10); - REQUIRE_FALSE(v[0].was_moved); - REQUIRE_FALSE(v[0].was_copied); + 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_FALSE(v[1].was_moved); - REQUIRE_FALSE(v[1].was_copied); + 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()); } @@ -1066,8 +1080,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") for (auto & elem : v) { - elem.was_copied = false; - elem.was_moved = false; + elem.reset_counts(); } auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); @@ -1077,16 +1090,30 @@ SCENARIO("Vector modifier move semantics", "[vector]") REQUIRE(v.size() == 3); REQUIRE(v[0].value == 10); - REQUIRE_FALSE(v[0].was_moved); - REQUIRE_FALSE(v[0].was_copied); + 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].was_moved); - REQUIRE_FALSE(v[1].was_copied); + 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].was_moved); - REQUIRE_FALSE(v[2].was_copied); + 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); } @@ -1417,8 +1444,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]") GIVEN("An empty vector and a const lvalue tracker") { - auto v = kstd::vector{}; - auto const tracker = kstd::tests::move_tracker{42}; + auto v = kstd::vector{}; + auto const tracker = kstd::tests::special_member_tracker{42}; WHEN("push_back is called with the const lvalue") { @@ -1428,8 +1455,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]") { REQUIRE(v.size() == 1); REQUIRE(v.back().value == 42); - REQUIRE(v.back().was_copied); - REQUIRE_FALSE(v.back().was_moved); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); } } @@ -1444,8 +1471,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]") REQUIRE(v.size() == 1); REQUIRE(v.capacity() == current_capacity); REQUIRE(v.back().value == 42); - REQUIRE(v.back().was_copied); - REQUIRE_FALSE(v.back().was_moved); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); } } } -- cgit v1.2.3 From 724b9693897642497ca5feee65546dc670bed722 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 1 Apr 2026 07:19:38 +0200 Subject: kstd/observer_ptr: extend test suite --- libs/kstd/include/kstd/observer_ptr | 8 +- libs/kstd/tests/src/observer_ptr.cpp | 165 ++++++++++++++++++++++++++++++----- 2 files changed, 149 insertions(+), 24 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/observer_ptr b/libs/kstd/include/kstd/observer_ptr index 97c9e5e..a328331 100644 --- a/libs/kstd/include/kstd/observer_ptr +++ b/libs/kstd/include/kstd/observer_ptr @@ -85,7 +85,7 @@ namespace kstd //! Get the currently watched object. //! //! @return A reference to the currently watched object. - [[nodiscard]] constexpr auto operator*() const noexcept -> std::add_lvalue_reference_t + [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t { throw_on_null(); return *m_ptr; @@ -94,7 +94,7 @@ namespace kstd //! Get the currently watched object. //! //! @return A pointer to the currently watched object. - [[nodiscard]] constexpr auto operator->() const noexcept -> element_type * + [[nodiscard]] constexpr auto operator->() const -> element_type * { throw_on_null(); return m_ptr; @@ -109,7 +109,7 @@ namespace kstd } //! Compare the observer pointer with another observer pointer. - //! + //!> //! @param other The other observer pointer to compare with. //! @return The result of the comparison. constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; @@ -118,7 +118,7 @@ namespace kstd //! Throw an exception if the observer pointer is null. //! //! @throws std::runtime_error if the observer pointer is null. - constexpr auto throw_on_null() const noexcept -> void + constexpr auto throw_on_null() const -> void { if (m_ptr == nullptr) { diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp index 2fe2226..5d94098 100644 --- a/libs/kstd/tests/src/observer_ptr.cpp +++ b/libs/kstd/tests/src/observer_ptr.cpp @@ -3,8 +3,28 @@ #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") @@ -59,17 +79,9 @@ SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") WHEN("copy constructing from an existing observer pointer with a compatible type") { - struct A - { - }; - - struct B : A - { - }; - - auto value = B{}; - auto ptr = kstd::observer_ptr(&value); - auto copy = kstd::observer_ptr(ptr); + 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") { @@ -112,6 +124,22 @@ SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") 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>); + } + } } } @@ -174,6 +202,24 @@ SCENARIO("Observer pointer modifiers", "[observer_ptr]") 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); + } + } } } @@ -181,14 +227,7 @@ SCENARIO("Observer pointer observers", "[observer_ptr]") { GIVEN("A non-null observer pointer") { - struct A - { - int value{}; - - constexpr auto operator<=>(A const & other) const noexcept = default; - }; - - auto value = A{1}; + auto value = Element{1}; auto ptr = kstd::observer_ptr{&value}; WHEN("getting the raw pointer") @@ -223,12 +262,98 @@ SCENARIO("Observer pointer observers", "[observer_ptr]") WHEN("converting the observer pointer to a raw pointer") { - auto raw_ptr = static_cast(ptr); + 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") + { + int arr[] = {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 -- cgit v1.2.3 From a2ff4ace21699fe2be2e0401af78790c01f78d85 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 11:45:55 +0200 Subject: kstd: move observer_ptr to bits --- libs/kstd/include/kstd/bits/observer_ptr.hpp | 157 +++++++++++++++++++++++++++ libs/kstd/include/kstd/memory | 9 +- libs/kstd/include/kstd/observer_ptr | 155 -------------------------- libs/kstd/tests/src/observer_ptr.cpp | 2 +- 4 files changed, 163 insertions(+), 160 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/observer_ptr.hpp delete mode 100644 libs/kstd/include/kstd/observer_ptr (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp new file mode 100644 index 0000000..43ea409 --- /dev/null +++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp @@ -0,0 +1,157 @@ +#ifndef KSTD_OBSERVER_PTR_HPP +#define KSTD_OBSERVER_PTR_HPP + +// IWYU pragma: private, include + +#include "kstd/os/error.hpp" + +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct observer_ptr + { + //! The type of the element being pointed to. + using element_type = ElementType; + + //! Construct an empty observer pointer. + constexpr observer_ptr() noexcept = default; + + //! Construct an empty observer pointer from a null pointer. + constexpr observer_ptr(std::nullptr_t) noexcept {} + + //! Construct an observer pointer from a raw pointer. + constexpr explicit observer_ptr(element_type * pointer) + : m_ptr{pointer} + {} + + //! Construct an observer pointer from another observer pointer. + template + requires std::convertible_to + constexpr observer_ptr(observer_ptr other) noexcept + : m_ptr{other.get()} + {} + + //! Copy construct an observer pointer. + constexpr observer_ptr(observer_ptr const & other) noexcept = default; + + //! Move construct an observer pointer. + constexpr observer_ptr(observer_ptr && other) noexcept = default; + + //! Stop watching the the watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto release() noexcept -> element_type * + { + return std::exchange(m_ptr, nullptr); + } + + //! Reset the observer pointer. + //! + //! @param pointer The new object to watch. + constexpr auto reset(element_type * pointer = nullptr) noexcept -> void + { + m_ptr = pointer; + } + + //! Swap the observer pointer with another observer pointer. + //! + //! @param other The other observer pointer to swap with. + constexpr auto swap(observer_ptr & other) noexcept -> void + { + std::swap(m_ptr, other.m_ptr); + } + + //! Get the currently watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto get() const noexcept -> element_type * + { + return m_ptr; + } + + //! Check if the observer pointer is watching an object. + //! + //! @return True if the observer pointer is watching an object, false otherwise. + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_ptr != nullptr; + } + + //! Get the currently watched object. + //! + //! @return A reference to the currently watched object. + [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t + { + throw_on_null(); + return *m_ptr; + } + + //! Get the currently watched object. + //! + //! @return A pointer to the currently watched object. + [[nodiscard]] constexpr auto operator->() const -> element_type * + { + throw_on_null(); + return m_ptr; + } + + //! Convert the observer pointer to a raw pointer. + //! + //! @return A pointer to the currently watched object. + constexpr explicit operator element_type *() const noexcept + { + return m_ptr; + } + + //! Compare the observer pointer with another observer pointer. + //!> + //! @param other The other observer pointer to compare with. + //! @return The result of the comparison. + constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; + + private: + //! Throw an exception if the observer pointer is null. + //! + //! @throws std::runtime_error if the observer pointer is null. + constexpr auto throw_on_null() const -> void + { + if (m_ptr == nullptr) + { + os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); + } + } + + //! The raw pointer to the watched object. + ElementType * m_ptr{}; + }; + + //! Swap two observer pointers. + //! + //! @param lhs The first observer pointer to swap. + //! @param rhs The second observer pointer to swap. + template + constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void + { + lhs.swap(rhs); + } + + //! Create an observer pointer from a raw pointer. + //! + //! @param pointer The raw pointer to create an observer pointer from. + //! @return An observer pointer to the given raw pointer. + template + constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr + { + return observer_ptr{pointer}; + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory index cab2fba..493f49a 100644 --- a/libs/kstd/include/kstd/memory +++ b/libs/kstd/include/kstd/memory @@ -1,7 +1,8 @@ -#ifndef KSTD_SHARED_POINTER_HPP -#define KSTD_SHARED_POINTER_HPP +#ifndef KSTD_MEMORY_HPP +#define KSTD_MEMORY_HPP -#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export -#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export +#include "kstd/bits/observer_ptr.hpp" // IWYU pragma: export +#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export +#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/observer_ptr b/libs/kstd/include/kstd/observer_ptr deleted file mode 100644 index a328331..0000000 --- a/libs/kstd/include/kstd/observer_ptr +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef KSTD_OBSERVER_PTR_HPP -#define KSTD_OBSERVER_PTR_HPP - -#include "kstd/os/error.hpp" - -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct observer_ptr - { - //! The type of the element being pointed to. - using element_type = ElementType; - - //! Construct an empty observer pointer. - constexpr observer_ptr() noexcept = default; - - //! Construct an empty observer pointer from a null pointer. - constexpr observer_ptr(std::nullptr_t) noexcept {} - - //! Construct an observer pointer from a raw pointer. - constexpr explicit observer_ptr(element_type * pointer) - : m_ptr{pointer} - {} - - //! Construct an observer pointer from another observer pointer. - template - requires std::convertible_to - constexpr observer_ptr(observer_ptr other) noexcept - : m_ptr{other.get()} - {} - - //! Copy construct an observer pointer. - constexpr observer_ptr(observer_ptr const & other) noexcept = default; - - //! Move construct an observer pointer. - constexpr observer_ptr(observer_ptr && other) noexcept = default; - - //! Stop watching the the watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto release() noexcept -> element_type * - { - return std::exchange(m_ptr, nullptr); - } - - //! Reset the observer pointer. - //! - //! @param pointer The new object to watch. - constexpr auto reset(element_type * pointer = nullptr) noexcept -> void - { - m_ptr = pointer; - } - - //! Swap the observer pointer with another observer pointer. - //! - //! @param other The other observer pointer to swap with. - constexpr auto swap(observer_ptr & other) noexcept -> void - { - std::swap(m_ptr, other.m_ptr); - } - - //! Get the currently watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto get() const noexcept -> element_type * - { - return m_ptr; - } - - //! Check if the observer pointer is watching an object. - //! - //! @return True if the observer pointer is watching an object, false otherwise. - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return m_ptr != nullptr; - } - - //! Get the currently watched object. - //! - //! @return A reference to the currently watched object. - [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t - { - throw_on_null(); - return *m_ptr; - } - - //! Get the currently watched object. - //! - //! @return A pointer to the currently watched object. - [[nodiscard]] constexpr auto operator->() const -> element_type * - { - throw_on_null(); - return m_ptr; - } - - //! Convert the observer pointer to a raw pointer. - //! - //! @return A pointer to the currently watched object. - constexpr explicit operator element_type *() const noexcept - { - return m_ptr; - } - - //! Compare the observer pointer with another observer pointer. - //!> - //! @param other The other observer pointer to compare with. - //! @return The result of the comparison. - constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; - - private: - //! Throw an exception if the observer pointer is null. - //! - //! @throws std::runtime_error if the observer pointer is null. - constexpr auto throw_on_null() const -> void - { - if (m_ptr == nullptr) - { - os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); - } - } - - //! The raw pointer to the watched object. - ElementType * m_ptr{}; - }; - - //! Swap two observer pointers. - //! - //! @param lhs The first observer pointer to swap. - //! @param rhs The second observer pointer to swap. - template - constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void - { - lhs.swap(rhs); - } - - //! Create an observer pointer from a raw pointer. - //! - //! @param pointer The raw pointer to create an observer pointer from. - //! @return An observer pointer to the given raw pointer. - template - constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr - { - return observer_ptr{pointer}; - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp index 5d94098..006ebde 100644 --- a/libs/kstd/tests/src/observer_ptr.cpp +++ b/libs/kstd/tests/src/observer_ptr.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include -- cgit v1.2.3 From e7af7ceea2324dcf7d2222986c09d1c478ee4c7e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 13:22:28 +0200 Subject: kstd: make string formattable --- libs/kstd/include/kstd/string | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 075422e..4ce19ce 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -1,6 +1,10 @@ #ifndef KSTD_STRING_HPP #define KSTD_STRING_HPP +#include "kstd/bits/format/context.hpp" +#include "kstd/bits/format/formatter.hpp" +#include "kstd/bits/format/formatter/string_view.hpp" + #include #include #include @@ -343,6 +347,15 @@ namespace kstd return !(lhs == rhs); } + template<> + struct formatter : formatter + { + auto format(string const & str, format_context & context) const -> void + { + formatter::format(str.view(), context); + } + }; + } // namespace kstd #endif \ No newline at end of file -- cgit v1.2.3 From d0c532af74d8d486d734904fd330d5dae7f49754 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 13:36:01 +0200 Subject: kapi: add basic device subsystem --- libs/kstd/include/kstd/bits/observer_ptr.hpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp index 43ea409..1c5da15 100644 --- a/libs/kstd/include/kstd/bits/observer_ptr.hpp +++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp @@ -44,6 +44,12 @@ namespace kstd //! Move construct an observer pointer. constexpr observer_ptr(observer_ptr && other) noexcept = default; + //! Copy assign an observer pointer. + constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default; + + //! Move assign an observer pointer. + constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default; + //! Stop watching the the watched object. //! //! @return The currently watched object, or nullptr if no object is being watched. -- cgit v1.2.3 From b84c4c9d8c90f3d3fd5a60de282278912fad2f04 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 13:59:27 +0200 Subject: x86_64/devices: implement ISA bus stub --- libs/kstd/include/kstd/bits/unique_ptr.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/include/kstd/bits/unique_ptr.hpp index e0870b1..f50335c 100644 --- a/libs/kstd/include/kstd/bits/unique_ptr.hpp +++ b/libs/kstd/include/kstd/bits/unique_ptr.hpp @@ -16,6 +16,9 @@ namespace kstd template struct unique_ptr { + template + friend struct unique_ptr; + /** * @brief Constructor. * @@ -40,6 +43,14 @@ namespace kstd */ unique_ptr(unique_ptr const &) = delete; + template + requires(std::is_convertible_v) + unique_ptr(unique_ptr && other) noexcept + : pointer(other.pointer) + { + other.pointer = nullptr; + } + /** * @brief Deleted copy assignment operator to enforce unique ownership. */ -- cgit v1.2.3 From 66ffd2ad8c793c4eea1527848fe4772e42595718 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 14:24:52 +0200 Subject: kapi: extract common bus code --- libs/kstd/include/kstd/bits/unique_ptr.hpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/include/kstd/bits/unique_ptr.hpp index f50335c..3d803b4 100644 --- a/libs/kstd/include/kstd/bits/unique_ptr.hpp +++ b/libs/kstd/include/kstd/bits/unique_ptr.hpp @@ -46,10 +46,8 @@ namespace kstd template requires(std::is_convertible_v) unique_ptr(unique_ptr && other) noexcept - : pointer(other.pointer) - { - other.pointer = nullptr; - } + : pointer{std::exchange(other.pointer, nullptr)} + {} /** * @brief Deleted copy assignment operator to enforce unique ownership. @@ -62,10 +60,8 @@ namespace kstd * @param other Unique pointer to move from. */ unique_ptr(unique_ptr && other) noexcept - : pointer(other.pointer) - { - other.pointer = nullptr; - } + : pointer{std::exchange(other.pointer, nullptr)} + {} /** * @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()). @@ -78,8 +74,7 @@ namespace kstd if (this != &other) { delete pointer; - pointer = other.pointer; - other.pointer = nullptr; + pointer = std::exchange(other.pointer, nullptr); } return *this; } @@ -134,9 +129,7 @@ namespace kstd */ auto release() -> T * { - T * temp = pointer; - pointer = nullptr; - return temp; + return std::exchange(pointer, nullptr); } /** @@ -150,8 +143,7 @@ namespace kstd */ auto reset(T * ptr = nullptr) -> void { - delete pointer; - pointer = ptr; + delete std::exchange(pointer, ptr); } /** -- cgit v1.2.3 From 3e80b6baa8f9666a9dd3cd4531bc68a3de4fee92 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 2 Apr 2026 15:18:05 +0200 Subject: kapi: allow for device searches --- libs/kstd/include/kstd/bits/flat_map.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp index 9455549..fe46203 100644 --- a/libs/kstd/include/kstd/bits/flat_map.hpp +++ b/libs/kstd/include/kstd/bits/flat_map.hpp @@ -45,7 +45,7 @@ namespace kstd::bits template requires(Index >= 0 && Index <= 1) - constexpr auto get() const noexcept -> decltype(auto) + [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto) { if constexpr (Index == 0) { -- cgit v1.2.3 From bd585306e31889ee4fce60abb79bc3b3a58e2b84 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 6 Apr 2026 13:11:15 +0200 Subject: kapi: add basic ACPI support --- libs/kstd/include/kstd/units | 7 +++- .../multiboot2/constants/information_id.hpp | 8 ++-- libs/multiboot2/include/multiboot2/information.hpp | 43 +++++++++++++++++++++- .../include/multiboot2/information/data.hpp | 12 +++++- 4 files changed, 63 insertions(+), 7 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/units b/libs/kstd/include/kstd/units index f6dcdb1..bc7e1b9 100644 --- a/libs/kstd/include/kstd/units +++ b/libs/kstd/include/kstd/units @@ -14,6 +14,11 @@ namespace kstd { using value_type = ValueType; + constexpr basic_unit() noexcept + : value{} + { + } + explicit constexpr basic_unit(value_type value) noexcept : value{value} {} @@ -142,4 +147,4 @@ namespace kstd } // namespace kstd -#endif \ No newline at end of file +#endif diff --git a/libs/multiboot2/include/multiboot2/constants/information_id.hpp b/libs/multiboot2/include/multiboot2/constants/information_id.hpp index be492eb..27c5300 100644 --- a/libs/multiboot2/include/multiboot2/constants/information_id.hpp +++ b/libs/multiboot2/include/multiboot2/constants/information_id.hpp @@ -57,10 +57,10 @@ namespace multiboot2 smbios_tables, //! A copy of RSDP as defined per ACPI 1.0 specification. - acpi_old_rsdp, + acpi_rsdp, - //! A copy of RSDP as defined per ACPI 2.0 or later specification. - acpi_new_rsdp, + //! A copy of XSDP as defined per ACPI 2.0 or later specification. + acpi_xsdp, //! The network information specified specified as per DHCP. networking_information, @@ -83,4 +83,4 @@ namespace multiboot2 } // namespace multiboot2 -#endif \ No newline at end of file +#endif diff --git a/libs/multiboot2/include/multiboot2/information.hpp b/libs/multiboot2/include/multiboot2/information.hpp index d2fac2e..a2ded56 100644 --- a/libs/multiboot2/include/multiboot2/information.hpp +++ b/libs/multiboot2/include/multiboot2/information.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -128,6 +129,26 @@ namespace multiboot2 return kstd::units::bytes{end_address - start_address}; } }; + + struct acpi_rsdp : vla_tag + { + using vla_tag::vla_tag; + + [[nodiscard]] auto pointer() const noexcept -> range_type + { + return {data(), size()}; + } + }; + + struct acpi_xsdp : vla_tag + { + using vla_tag::vla_tag; + + [[nodiscard]] auto pointer() const noexcept -> range_type + { + return {data(), size()}; + } + }; struct information_view { @@ -245,6 +266,26 @@ namespace multiboot2 std::views::transform(transform_module); } + [[nodiscard]] auto maybe_acpi_rsdp() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto acpi_rsdp() const noexcept -> acpi_rsdp + { + return maybe_acpi_rsdp().value(); + } + + [[nodiscard]] auto maybe_acpi_xsdp() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto acpi_xsdp() const noexcept -> acpi_xsdp + { + return maybe_acpi_xsdp().value(); + } + private: template [[nodiscard]] constexpr auto get() const noexcept -> std::optional @@ -264,4 +305,4 @@ namespace multiboot2 } // namespace multiboot2 -#endif \ No newline at end of file +#endif diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/include/multiboot2/information/data.hpp index 3b07d20..315eb39 100644 --- a/libs/multiboot2/include/multiboot2/information/data.hpp +++ b/libs/multiboot2/include/multiboot2/information/data.hpp @@ -167,8 +167,18 @@ namespace multiboot2 std::uint32_t end_address; }; + //! A copy of the ACPI RSDP + struct acpi_rsdp : tag_data + { + }; + + //! A copy of the ACPI XSDP + struct acpi_xsdp : tag_data + { + }; + } // namespace data } // namespace multiboot2 -#endif \ No newline at end of file +#endif -- cgit v1.2.3 From 4d938cd31a35cd4322fe914edd568faa5391c9c2 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 6 Apr 2026 15:10:04 +0200 Subject: kernel/acpi: implement basic table discovery --- libs/kstd/include/kstd/flat_map | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map index 6ffbbcf..3b13754 100644 --- a/libs/kstd/include/kstd/flat_map +++ b/libs/kstd/include/kstd/flat_map @@ -205,6 +205,24 @@ namespace kstd 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. -- cgit v1.2.3 From c3f7b747f02a79b34ed914c54ce74be973b17af1 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 10 Apr 2026 17:39:14 +0200 Subject: kapi: extract ACPI functionality to libs --- libs/CMakeLists.txt | 3 +- libs/acpi/CMakeLists.txt | 29 ++++++++++ libs/acpi/acpi/acpi.hpp | 10 ++++ libs/acpi/acpi/checksum.cpp | 19 +++++++ libs/acpi/acpi/checksum.hpp | 20 +++++++ libs/acpi/acpi/madt.cpp | 68 ++++++++++++++++++++++++ libs/acpi/acpi/madt.hpp | 92 ++++++++++++++++++++++++++++++++ libs/acpi/acpi/pointers.cpp | 55 +++++++++++++++++++ libs/acpi/acpi/pointers.hpp | 72 +++++++++++++++++++++++++ libs/acpi/acpi/sdt.cpp | 51 ++++++++++++++++++ libs/acpi/acpi/sdt.hpp | 119 ++++++++++++++++++++++++++++++++++++++++++ libs/acpi/acpi/table_type.hpp | 17 ++++++ 12 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 libs/acpi/CMakeLists.txt create mode 100644 libs/acpi/acpi/acpi.hpp create mode 100644 libs/acpi/acpi/checksum.cpp create mode 100644 libs/acpi/acpi/checksum.hpp create mode 100644 libs/acpi/acpi/madt.cpp create mode 100644 libs/acpi/acpi/madt.hpp create mode 100644 libs/acpi/acpi/pointers.cpp create mode 100644 libs/acpi/acpi/pointers.hpp create mode 100644 libs/acpi/acpi/sdt.cpp create mode 100644 libs/acpi/acpi/sdt.hpp create mode 100644 libs/acpi/acpi/table_type.hpp (limited to 'libs') diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 58d9796..2b61992 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory("acpi" EXCLUDE_FROM_ALL SYSTEM) add_subdirectory("elf" EXCLUDE_FROM_ALL SYSTEM) add_subdirectory("kstd" EXCLUDE_FROM_ALL SYSTEM) -add_subdirectory("multiboot2" EXCLUDE_FROM_ALL SYSTEM) \ No newline at end of file +add_subdirectory("multiboot2" EXCLUDE_FROM_ALL SYSTEM) diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt new file mode 100644 index 0000000..c6e63b9 --- /dev/null +++ b/libs/acpi/CMakeLists.txt @@ -0,0 +1,29 @@ +add_library("acpi" STATIC) +add_library("libs::acpi" ALIAS "acpi") + +target_include_directories("acpi" PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +file(GLOB_RECURSE ACPI_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "acpi/*.hpp" +) + +target_sources("acpi" PRIVATE + "acpi/checksum.cpp" + "acpi/madt.cpp" + "acpi/pointers.cpp" + "acpi/sdt.cpp" +) + +target_sources("acpi" PUBLIC + FILE_SET HEADERS + BASE_DIRS "acpi" + FILES + ${ACPI_HEADERS} +) + +target_link_libraries("acpi" PUBLIC + "libs::kstd" +) diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp new file mode 100644 index 0000000..fb358cc --- /dev/null +++ b/libs/acpi/acpi/acpi.hpp @@ -0,0 +1,10 @@ +#ifndef ACPI_ACPI_HPP +#define ACPI_ACPI_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif diff --git a/libs/acpi/acpi/checksum.cpp b/libs/acpi/acpi/checksum.cpp new file mode 100644 index 0000000..56275c8 --- /dev/null +++ b/libs/acpi/acpi/checksum.cpp @@ -0,0 +1,19 @@ +#include + +#include +#include +#include +#include + +namespace acpi +{ + + auto validate_checksum(std::span data) -> bool + { + auto sum = std::ranges::fold_left(data, std::uint8_t{}, [](auto acc, auto byte) { + return static_cast(acc + static_cast(byte)); + }); + return sum == 0; + } + +} // namespace acpi diff --git a/libs/acpi/acpi/checksum.hpp b/libs/acpi/acpi/checksum.hpp new file mode 100644 index 0000000..a92c242 --- /dev/null +++ b/libs/acpi/acpi/checksum.hpp @@ -0,0 +1,20 @@ +#ifndef ACPI_CHECKSUM_HPP +#define ACPI_CHECKSUM_HPP + +// IWYU pragma: private, include + +#include +#include + +namespace acpi +{ + + //! Validate and ACPI entity checksum. + //! + //! @param data The data to validate the checksum of. + //! @return true iff. the checksum is valid, false otherwise. + auto validate_checksum(std::span data) -> bool; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/madt.cpp b/libs/acpi/acpi/madt.cpp new file mode 100644 index 0000000..40289cf --- /dev/null +++ b/libs/acpi/acpi/madt.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include + +namespace acpi +{ + + auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t + { + return static_cast(m_local_interrupt_controller_address); + } + + auto madt::flags() const noexcept -> std::uint32_t + { + return m_flags; + } + + auto madt_entry::type() const noexcept -> types + { + return static_cast(m_type); + } + + auto madt_entry::length() const noexcept -> std::size_t + { + return m_length; + } + + auto processor_local_apic::apic_id() const noexcept -> std::uint8_t + { + return m_apic_id; + } + + auto processor_local_apic::active_flags() const noexcept -> flags + { + return static_cast(m_flags); + } + + auto processor_local_apic::processor_id() const noexcept -> std::uint32_t + { + return m_processor_id; + } + + auto madt::begin() const noexcept -> iterator + { + auto base = reinterpret_cast(this); + base += sizeof(madt); + auto limit = reinterpret_cast(this); + limit += length().value; + return iterator{reinterpret_cast(base), reinterpret_cast(limit)}; + } + + auto madt::cbegin() const noexcept -> iterator + { + return begin(); + } + + auto madt::end() const noexcept -> iterator + { + return {}; + } + + auto madt::cend() const noexcept -> iterator + { + return end(); + } + +} // namespace acpi diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp new file mode 100644 index 0000000..f680430 --- /dev/null +++ b/libs/acpi/acpi/madt.hpp @@ -0,0 +1,92 @@ +#ifndef ACPI_ACPI_MADT_HPP +#define ACPI_ACPI_MADT_HPP + +// IWYU pragma: private, include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace acpi +{ + + struct [[gnu::packed]] madt_entry + { + enum struct types : std::uint8_t + { + processor_local_apic, + io_apic, + io_apic_interrupt_source_override, + io_apic_nmi_source, + local_apic_nmi_interrupts, + local_apic_address_override, + processor_local_x2_apic, + }; + + [[nodiscard]] auto type() const noexcept -> types; + [[nodiscard]] auto length() const noexcept -> std::size_t; + + private: + std::uint8_t m_type; + std::uint8_t m_length; + }; + + //! The common header for all + struct [[gnu::packed]] madt : sdt + { + using iterator = sdt_iterator; + using const_iterator = iterator; + + [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; + [[nodiscard]] auto flags() const noexcept -> std::uint32_t; + + [[nodiscard]] auto begin() const noexcept -> iterator; + [[nodiscard]] auto cbegin() const noexcept -> iterator; + [[nodiscard]] auto end() const noexcept -> iterator; + [[nodiscard]] auto cend() const noexcept -> iterator; + + private: + std::uint32_t m_local_interrupt_controller_address; + std::uint32_t m_flags; + }; + + struct [[gnu::packed]] processor_local_apic : madt_entry + { + enum struct flags : std::uint32_t + { + processor_enabled = 1, + online_capable = 2, + }; + + [[nodiscard]] auto apic_id() const noexcept -> std::uint8_t; + [[nodiscard]] auto active_flags() const noexcept -> flags; + [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t; + + private: + std::uint8_t m_processor_id; + std::uint8_t m_apic_id; + std::uint32_t m_flags; + }; + + constexpr char const madt_table_signature[] = "APIC"; // NOLINT + + template<> + struct table_type + { + using type = madt; + }; + +} // namespace acpi + +template<> +struct kstd::ext::is_bitfield_enum : std::true_type +{ +}; + +#endif diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp new file mode 100644 index 0000000..b206cc6 --- /dev/null +++ b/libs/acpi/acpi/pointers.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include + +#include +#include +#include +#include + +namespace acpi +{ + + auto rsdp::oem_id() const noexcept -> std::string_view + { + return {m_oem_id.data(), m_oem_id.size()}; + } + + auto rsdp::revision() const noexcept -> std::uint8_t + { + return m_revision; + } + + auto rsdp::signature() const noexcept -> std::string_view + { + return {m_signature.data(), m_signature.size()}; + } + + auto rsdp::table_address() const noexcept -> std::uintptr_t + { + auto raw = std::bit_cast(m_rsdt_address); + return static_cast(raw); + } + + auto rsdp::validate() const noexcept -> bool + { + return validate_checksum({reinterpret_cast(this), sizeof(rsdp)}); + } + + auto xsdp::length() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{m_length}; + } + + auto xsdp::table_address() const noexcept -> std::uintptr_t + { + return std::bit_cast(m_xsdt_address); + } + + auto xsdp::validate() const noexcept -> bool + { + return validate_checksum({reinterpret_cast(this), m_length}); + } + +} // namespace acpi diff --git a/libs/acpi/acpi/pointers.hpp b/libs/acpi/acpi/pointers.hpp new file mode 100644 index 0000000..5771e7d --- /dev/null +++ b/libs/acpi/acpi/pointers.hpp @@ -0,0 +1,72 @@ +#ifndef ACPI_POINTERS_HPP +#define ACPI_POINTERS_HPP + +// IWYU pragma: private, include + +#include + +#include +#include +#include +#include + +namespace acpi +{ + + //! A pointer to the Root System Description Table. + struct [[gnu::packed]] rsdp + { + //! Get the ID of the OEM, usually a vendor name. + [[nodiscard]] auto oem_id() const noexcept -> std::string_view; + + //! Get the revision of the ACPI Root System Description Table. + //! + //! @note Revisions greater or equal to two indicate that the system uses the Extended System Description Table, and + //! as such that table should be use to query information. + [[nodiscard]] auto revision() const noexcept -> std::uint8_t; + + //! Get the signature of this pointer. + //! + //! A valid RSDP must always carry the signature "RSD PTR ". + [[nodiscard]] auto signature() const noexcept -> std::string_view; + + //! Get the physical address of the pointed-to Root System Description Table. + [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t; + + //! Validate the checksum of this RSDP. + //! + //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise. + [[nodiscard]] auto validate() const noexcept -> bool; + + private: + std::array m_signature; + [[maybe_unused]] std::uint8_t m_checksum; + std::array m_oem_id; + std::uint8_t m_revision; + std::array m_rsdt_address; + }; + + //! A pointer to the Extended System Description Table. + struct [[gnu::packed]] xsdp : rsdp + { + //! Get the length of the data contained in this pointer. + [[nodiscard]] auto length() const noexcept -> kstd::units::bytes; + + //! Get the physical address of the pointed-to Extended System Description Table. + [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t; + + //! Validate the checksum of this XSDP. + //! + //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise. + [[nodiscard]] auto validate() const noexcept -> bool; + + private: + std::uint32_t m_length; + std::array m_xsdt_address; + [[maybe_unused]] std::uint8_t m_extended_checksum; + [[maybe_unused]] std::array m_reserved; + }; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/sdt.cpp b/libs/acpi/acpi/sdt.cpp new file mode 100644 index 0000000..c2b9d68 --- /dev/null +++ b/libs/acpi/acpi/sdt.cpp @@ -0,0 +1,51 @@ +#include + +#include + +#include +#include + +namespace acpi +{ + + auto sdt::creator_revision() const noexcept -> std::uint32_t + { + return m_creator_revision; + } + + auto sdt::creator_id() const noexcept -> std::uint32_t + { + return m_creator_id; + } + + auto sdt::length() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{m_length}; + } + + auto sdt::oem_id() const noexcept -> std::string_view + { + return {m_oem_id.data(), m_oem_id.size()}; + } + + auto sdt::oem_revision() const noexcept -> std::uint32_t + { + return m_oem_revision; + } + + auto sdt::oem_table_id() const noexcept -> std::string_view + { + return {m_oem_table_id.data(), m_oem_table_id.size()}; + } + + auto sdt::revision() const noexcept -> std::uint8_t + { + return m_revision; + } + + auto sdt::signature() const noexcept -> std::string_view + { + return {m_signature.data(), m_signature.size()}; + } + +} // namespace acpi diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp new file mode 100644 index 0000000..9e5ec89 --- /dev/null +++ b/libs/acpi/acpi/sdt.hpp @@ -0,0 +1,119 @@ +#ifndef ACPI_SDT_HPP +#define ACPI_SDT_HPP + +// IWYU pragma: private, include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace acpi +{ + + template + struct sdt_iterator + { + using iterator_category = std::forward_iterator_tag; + using value_type = EntryType; + using pointer = value_type *; + using reference = value_type &; + using difference_type = std::ptrdiff_t; + + constexpr sdt_iterator() noexcept = default; + + constexpr sdt_iterator(pointer entry, pointer limit) noexcept + : m_entry{entry} + , m_limit{limit} + {} + + constexpr auto operator++() noexcept -> sdt_iterator & + { + auto decayed = std::bit_cast(m_entry); + decayed += m_entry->length(); + m_entry = std::bit_cast(decayed); + return *this; + } + + constexpr auto operator++(int) noexcept -> sdt_iterator + { + auto copy = *this; + ++*this; + return copy; + } + + constexpr auto operator*() const noexcept -> reference + { + return *m_entry; + } + + constexpr auto operator==(sdt_iterator const & other) const noexcept -> bool + { + return m_entry == other.m_entry || (is_end() && other.is_end()); + } + + private: + [[nodiscard]] constexpr auto is_end() const noexcept -> bool + { + return m_entry == m_limit; + } + + pointer m_entry{}; + pointer m_limit{}; + }; + + //! The common base of all System Description Tables. + struct [[gnu::packed]] sdt + { + //! Get the revision of the utility used to create this table. + [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t; + + //! Get the vendor ID of the utility used to create this table. + [[nodiscard]] auto creator_id() const noexcept -> std::uint32_t; + + //! Get the length of the entire table, including this header. + [[nodiscard]] auto length() const noexcept -> kstd::units::bytes; + + //! Get the ID of the OEM. + [[nodiscard]] auto oem_id() const noexcept -> std::string_view; + + //! Get the OEMs revision number of this table. + [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t; + + //! Get the OEMs ID of this table. + [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view; + + //! Get the revision number of the structure of this table. + [[nodiscard]] auto revision() const noexcept -> std::uint8_t; + + //! Get the signature of this table. + [[nodiscard]] auto signature() const noexcept -> std::string_view; + + private: + std::array m_signature; + std::uint32_t m_length; + std::uint8_t m_revision; + [[maybe_unused]] std::uint8_t m_checksum; + std::array m_oem_id; + std::array m_oem_table_id; + std::uint32_t m_oem_revision; + std::uint32_t m_creator_id; + std::uint32_t m_creator_revision; + }; + + constexpr char const rsdt_table_signature[] = "RSDT"; // NOLINT + + template<> + struct table_type + { + using type = sdt; + }; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/table_type.hpp b/libs/acpi/acpi/table_type.hpp new file mode 100644 index 0000000..7fdd7e3 --- /dev/null +++ b/libs/acpi/acpi/table_type.hpp @@ -0,0 +1,17 @@ +#ifndef ACPI_TABLE_TYPE_HPP +#define ACPI_TABLE_TYPE_HPP + +// IWYU pragma: private, include + +namespace acpi +{ + + template + struct table_type; + + template + using table_type_t = typename table_type::type; + +} // namespace acpi + +#endif -- cgit v1.2.3 From 869ff69ebae160006e31eb0f24ed927bb65f3c63 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Wed, 8 Apr 2026 18:51:30 +0200 Subject: implement vector resize --- libs/kstd/include/kstd/vector | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 9e41cb6..e51cbac 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -559,6 +559,45 @@ namespace kstd m_size = old_size; } + //! 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 { -- cgit v1.2.3 From e38fd2056a8cab7a3c6f3da8150fed69530bde6c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:14:08 +0200 Subject: kstd: introduce output buffer for formatting --- libs/kstd/include/kstd/bits/format/context.hpp | 12 ++++----- .../include/kstd/bits/format/output_buffer.hpp | 30 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/output_buffer.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 478a48f..e8d0302 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include #include "arg.hpp" +#include "kstd/bits/format/output_buffer.hpp" #include @@ -29,11 +30,7 @@ namespace kstd struct format_context { - using writer_function = void(void *, std::string_view); using format_args = std::span; - - writer_function * writer{}; - void * user_data{}; format_args args{}; [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & @@ -47,13 +44,16 @@ namespace kstd constexpr auto push(std::string_view string) -> void { - writer(user_data, string); + m_buffer->push(string); } constexpr auto push(char character) -> void { - writer(user_data, std::string_view(&character, 1)); + m_buffer->push(character); } + + private: + bits::format::output_buffer * m_buffer; }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..be2034f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,30 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +#include + +namespace kstd::bits::format +{ + + //! An abstract interface for formatted output buffers. + //! + //! This interface is intended to be use for functions dealing with string formatting, like the print and the format + //! family. + struct output_buffer + { + virtual ~output_buffer() = default; + + //! Push a text segment into the buffer. + //! + //! @param text The text segment to push. + virtual auto push(std::string_view text) -> void = 0; + + //! Push a single character into the buffer. + //! + //! @param character The character to push into the buffer. + virtual auto push(char character) -> void = 0; + }; + +} // namespace kstd::bits::format + +#endif \ No newline at end of file -- cgit v1.2.3 From 98fc294deb6f7c27eda3f9ed3b616572a1ab2009 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:37:20 +0200 Subject: kstd/format: hook up vformat_to --- .../include/kstd/bits/format/output_buffer.hpp | 2 + libs/kstd/include/kstd/bits/format/vformat.hpp | 65 ++++++++++++++++++++++ libs/kstd/include/kstd/format | 2 + 3 files changed, 69 insertions(+) create mode 100644 libs/kstd/include/kstd/bits/format/vformat.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp index be2034f..fd7a2b4 100644 --- a/libs/kstd/include/kstd/bits/format/output_buffer.hpp +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -1,6 +1,8 @@ #ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP #define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +// IWYU pragma: private, include + #include namespace kstd::bits::format diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp new file mode 100644 index 0000000..90b74a9 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -0,0 +1,65 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + namespace bits::format + { + //! Format a string with the given arguments into the given buffer. + //! + //! External implementations of `vprint` may call this function. + //! + //! @param buffer The buffer to format into. + //! @param format The format string to use. + //! @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 + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + auto release() -> string &&; + + private: + string m_result{}; + }; + + struct string_iterator_writer : output_buffer + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + private: + string::iterator m_iter{}; + }; + + } // namespace bits::format + + //! Format a given string with the provided arguments. + //! + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A new string containing the result of the format operation. + template + auto format(format_string...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + return buffer.release(); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index adee5f8..d11c221 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -13,7 +13,9 @@ #include "bits/format/formatter/pointer.hpp" // IWYU pragma: export #include "bits/format/formatter/range.hpp" // IWYU pragma: export #include "bits/format/formatter/string_view.hpp" // IWYU pragma: export +#include "bits/format/output_buffer.hpp" // IWYU pragma: export #include "bits/format/parse_context.hpp" // IWYU pragma: export #include "bits/format/string.hpp" // IWYU pragma: export +#include "bits/format/vformat.hpp" // IWYU pragma: export #endif \ No newline at end of file -- cgit v1.2.3 From 45091789eb9e49856ea6c2f3606639d43f4584e7 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:56:39 +0200 Subject: kstd: move formatting implementation to kstd --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/bits/format/context.hpp | 5 + .../include/kstd/bits/format/formatter/bool.hpp | 4 +- .../kstd/bits/format/formatter/integral.hpp | 4 +- libs/kstd/include/kstd/bits/format/specifiers.hpp | 6 +- libs/kstd/include/kstd/bits/format/vformat.hpp | 2 + libs/kstd/include/kstd/vector | 2 +- libs/kstd/src/vformat.cpp | 224 +++++++++++++++++++++ 8 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 libs/kstd/src/vformat.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 240118e..f7c771b 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources("kstd" PRIVATE "src/os/error.cpp" "src/mutex.cpp" + "src/vformat.cpp" ) file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*") diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index e8d0302..7f392a0 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -33,6 +33,11 @@ namespace kstd using format_args = std::span; format_args args{}; + format_context(bits::format::output_buffer & buffer, format_args args) + : args{args} + , m_buffer{&buffer} + {} + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & { if (id >= args.size()) diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index 336e1b0..e371cec 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -51,11 +51,11 @@ namespace kstd auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; auto final_width = 0uz; - if (specifiers.width_mode == bits::format::width_mode::static_value) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index 4912a44..e5a234a 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -64,11 +64,11 @@ namespace kstd auto format(T value, format_context & context) const -> void { auto final_width = 0uz; - if (specifiers.width_mode == bits::format::width_mode::static_value) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 9bc66c7..18c6f66 100644 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -43,7 +43,7 @@ namespace kstd::bits::format bool alternative_form{}; bool zero_pad{}; - width_mode width_mode{}; + width_mode mode{}; std::size_t width_value{}; char type{}; }; @@ -133,7 +133,7 @@ namespace kstd::bits::format if (it != end && *it == '{') { - specs.width_mode = width_mode::dynamic_argument_id; + specs.mode = width_mode::dynamic_argument_id; std::advance(it, 1); auto argument_id = 0uz; @@ -160,7 +160,7 @@ namespace kstd::bits::format } else if (it != end && *it >= '0' && *it <= '9') { - specs.width_mode = width_mode::static_value; + specs.mode = width_mode::static_value; while (it != end && *it >= '0' && *it <= '9') { specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index 90b74a9..d032c3a 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -38,6 +38,8 @@ namespace kstd struct string_iterator_writer : output_buffer { + explicit string_iterator_writer(string::iterator iterator); + auto push(std::string_view text) -> void override; auto push(char character) -> void override; diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index e51cbac..79593c6 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -111,7 +111,7 @@ namespace kstd allocator_type const & allocator = allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) : m_allocator{allocator} - , m_size{std::ranges::distance(first, last)} + , m_size{static_cast(std::ranges::distance(first, last))} , m_capacity{m_size} , m_data{allocate_n(m_capacity)} { diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp new file mode 100644 index 0000000..51aca84 --- /dev/null +++ b/libs/kstd/src/vformat.cpp @@ -0,0 +1,224 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace kstd::bits::format +{ + + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void + { + auto context = kstd::format_context{buffer, args}; + auto parse_context = kstd::format_parse_context{format, args.size()}; + + auto it = parse_context.begin(); + auto end = parse_context.end(); + + while (it != end) + { + if (*it != '{' && *it != '}') + { + auto start = it; + while (it != end && *it != '{' && *it != '}') + { + std::advance(it, 1); + } + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); + continue; + } + + if (*it == '{') + { + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } + + parse_context.advance_to(it); + auto index = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + } + + parse_context.advance_to(it); + + if (index < args.size()) + { + auto const & arg = args[index]; + switch (arg.type) + { + case kstd::bits::format::arg_type::boolean: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.boolean, context); + break; + } + case kstd::bits::format::arg_type::character: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.character, context); + break; + } + case kstd::bits::format::arg_type::integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.integer, context); + break; + } + case kstd::bits::format::arg_type::unsigned_integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.unsigned_integer, context); + break; + } + case kstd::bits::format::arg_type::string_view: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.string_view, context); + break; + } + case kstd::bits::format::arg_type::c_string: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.c_string, context); + break; + } + case kstd::bits::format::arg_type::pointer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.pointer, context); + break; + } + case kstd::bits::format::arg_type::user_defined: + { + if (arg.value.user_defined.format) + { + arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + break; + } + default: + { + context.push("{fmt-err: unknown-type}"); + break; + } + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } + } + else if (*it == '}') + { + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } + } + } + } + + auto string_writer::push(std::string_view text) -> void + { + m_result.append(text); + } + + auto string_writer::push(char character) -> void + { + m_result.push_back(character); + } + + auto string_writer::release() -> string && + { + return std::move(m_result); + } + + string_iterator_writer::string_iterator_writer(string::iterator it) + : m_iter{it} + {} + + auto string_iterator_writer::push(std::string_view text) -> void + { + std::ranges::for_each(text, [this](auto c) { push(c); }); + } + + auto string_iterator_writer::push(char character) -> void + { + *m_iter++ = character; + } + +} // namespace kstd::bits::format \ No newline at end of file -- cgit v1.2.3 From a0720bbe3cdfe3174e3d064356b21f0fcd37832e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 15:05:38 +0200 Subject: kstd/format: add kstd::format_to --- libs/kstd/include/kstd/bits/format/vformat.hpp | 42 +++++++++++++++++++++++--- libs/kstd/src/vformat.cpp | 15 --------- 2 files changed, 37 insertions(+), 20 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index d032c3a..69c7f33 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include @@ -36,15 +38,30 @@ namespace kstd string m_result{}; }; - struct string_iterator_writer : output_buffer + template Output> + struct iterator_writer : output_buffer { - explicit string_iterator_writer(string::iterator iterator); + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} - auto push(std::string_view text) -> void override; - auto push(char character) -> void override; + auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output); + } + + auto push(char character) -> void override + { + *m_output++ = character; + } private: - string::iterator m_iter{}; + Output m_output{}; }; } // namespace bits::format @@ -62,6 +79,21 @@ namespace kstd return buffer.release(); } + //! Format a given string with the provided arguments. + //! + //! @param iterator The iterator to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return An iterator past the last element written. + template Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + return buffer.iterator(); + } + } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp index 51aca84..b7c5121 100644 --- a/libs/kstd/src/vformat.cpp +++ b/libs/kstd/src/vformat.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -207,18 +206,4 @@ namespace kstd::bits::format return std::move(m_result); } - string_iterator_writer::string_iterator_writer(string::iterator it) - : m_iter{it} - {} - - auto string_iterator_writer::push(std::string_view text) -> void - { - std::ranges::for_each(text, [this](auto c) { push(c); }); - } - - auto string_iterator_writer::push(char character) -> void - { - *m_iter++ = character; - } - } // namespace kstd::bits::format \ No newline at end of file -- cgit v1.2.3 From cd1dd2037cbe1d5f1362202d3127640406b468b8 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 15:38:08 +0200 Subject: kstd: add basic format and format_to tests --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/bits/format/vformat.hpp | 6 +- libs/kstd/include/kstd/string | 20 +++++ libs/kstd/tests/src/format.cpp | 116 +++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 libs/kstd/tests/src/format.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index f7c771b..ced3138 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -44,6 +44,7 @@ if(CMAKE_CROSSCOMPILING) else() add_executable("kstd_tests" "tests/src/flat_map.cpp" + "tests/src/format.cpp" "tests/src/vector.cpp" "tests/src/observer_ptr.cpp" "tests/src/os_panic.cpp" diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index 69c7f33..4fec7dd 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -52,7 +52,7 @@ namespace kstd auto push(std::string_view text) -> void override { - m_output = std::ranges::copy(text, m_output); + m_output = std::ranges::copy(text, m_output).out; } auto push(char character) -> void override @@ -75,7 +75,7 @@ namespace kstd auto format(format_string...> format, ArgumentTypes &&... args) -> string { auto buffer = bits::format::string_writer{}; - bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); return buffer.release(); } @@ -90,7 +90,7 @@ namespace kstd ArgumentTypes &&... args) -> Output { auto buffer = bits::format::iterator_writer{iterator}; - bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); return buffer.iterator(); } diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 4ce19ce..58c4a08 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -347,6 +347,26 @@ namespace kstd return !(lhs == rhs); } + [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool + { + return lhs.view() == std::string_view{rhs}; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool + { + return std::string_view{lhs} == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + template<> struct formatter : formatter { diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp new file mode 100644 index 0000000..4915e50 --- /dev/null +++ b/libs/kstd/tests/src/format.cpp @@ -0,0 +1,116 @@ +#include +#include +#include + +#include + +#include +#include + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with 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") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, -100); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, -200, 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false -200"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with 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") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + 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"); + } + } + } +} \ No newline at end of file -- cgit v1.2.3 From 3795115641bf5c1d1a3d60313408ba462057ba18 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 16:18:00 +0200 Subject: kstd/format: add support for char formatting --- .../include/kstd/bits/format/formatter/char.hpp | 94 ++++++++++++++++++++++ libs/kstd/include/kstd/format | 1 + libs/kstd/tests/src/format.cpp | 8 +- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/formatter/char.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp new file mode 100644 index 0000000..ddfefe5 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/char.hpp @@ -0,0 +1,94 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" +#include "integral.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) + { + bits::format::error("Invalid format specifiers for 'char'"); + } + + if (it != end && *it != '}') + { + if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for char."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(char value, format_context & context) const -> void + { + if (specifiers.type == '\0' || specifiers.type == 'c') + { + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = + bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(value); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + else + { + formatter::format(static_cast(value), context); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index d11c221..047ea5c 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -7,6 +7,7 @@ #include "bits/format/formatter.hpp" // IWYU pragma: export #include "bits/format/formatter/bool.hpp" // IWYU pragma: export #include "bits/format/formatter/byte.hpp" // IWYU pragma: export +#include "bits/format/formatter/char.hpp" // IWYU pragma: export #include "bits/format/formatter/cstring.hpp" // IWYU pragma: export #include "bits/format/formatter/integral.hpp" // IWYU pragma: export #include "bits/format/formatter/ordering.hpp" // IWYU pragma: export diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp index 4915e50..73c8102 100644 --- a/libs/kstd/tests/src/format.cpp +++ b/libs/kstd/tests/src/format.cpp @@ -40,21 +40,21 @@ SCENARIO("Formatting to a new string", "[format]") WHEN("calling format with the same number of arguments as there are placeholders") { - auto result = kstd::format(fmt, 1, true, -100); + auto result = kstd::format(fmt, 1, true, 'a'); THEN("the result is the formatted string") { - REQUIRE(result == "Here are some placeholders: 1 true -100"); + REQUIRE(result == "Here are some placeholders: 1 true a"); } } WHEN("calling format with too many arguments") { - auto result = kstd::format(fmt, 2, false, -200, 4, 5, 6); + 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 -200"); + REQUIRE(result == "Here are some placeholders: 2 false b"); } } } -- cgit v1.2.3 From 2f4591dbb173d602368437e1dd8c788b0bedd4aa Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 14 Apr 2026 13:23:52 +0200 Subject: kstd: add basic flat_map::try_insert --- libs/kstd/include/kstd/flat_map | 27 +++++++++++++++++++++++++ libs/kstd/include/kstd/vector | 39 +++++++++++++++++++++++++++++++++++ libs/kstd/tests/src/vector.cpp | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) (limited to 'libs') diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map index 3b13754..f12b1b5 100644 --- a/libs/kstd/include/kstd/flat_map +++ b/libs/kstd/include/kstd/flat_map @@ -256,6 +256,33 @@ namespace kstd }; } + //! 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. diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 79593c6..e1b8b38 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -728,6 +728,45 @@ namespace kstd return begin() + prefix_size; } + template + constexpr auto emplace(const_iterator position, Args &&... args) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + emplace_back(std::forward(args)...); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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; + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value_type{std::forward(args)...}; + } + + ++m_size; + return begin() + prefix_size; + } + //! Erase an element at a given position. //! //! @note This function will panic if position == end() diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 81bf32f..5faaaff 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -463,6 +463,33 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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(), 42); @@ -526,6 +553,24 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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("push_back is called with a reference to an internal element") { v.shrink_to_fit(); -- cgit v1.2.3 From eacc1becd1308a01a7ffcddf7c8910c8dc708939 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 14 Apr 2026 13:52:19 +0200 Subject: acpi: begin test implementation --- libs/acpi/CMakeLists.txt | 19 +++++++++++++++++++ libs/acpi/acpi/pointers.cpp | 4 ++-- libs/acpi/acpi/pointers.test.cpp | 29 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 libs/acpi/acpi/pointers.test.cpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index c6e63b9..b8face4 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -27,3 +27,22 @@ target_sources("acpi" PUBLIC target_link_libraries("acpi" PUBLIC "libs::kstd" ) + +if(NOT CMAKE_CROSSCOMPILING) + add_executable("acpi_tests" + "acpi/pointers.test.cpp" + ) + + target_link_libraries("acpi_tests" PRIVATE + "Catch2::Catch2WithMain" + "libs::acpi" + ) + + set_target_properties("acpi_tests" PROPERTIES + C_CLANG_TIDY "" + CXX_CLANG_TIDY "" + EXCLUDE_FROM_ALL NO + ) + + catch_discover_tests("acpi_tests") +endif() diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp index b206cc6..8a8629f 100644 --- a/libs/acpi/acpi/pointers.cpp +++ b/libs/acpi/acpi/pointers.cpp @@ -34,7 +34,7 @@ namespace acpi auto rsdp::validate() const noexcept -> bool { - return validate_checksum({reinterpret_cast(this), sizeof(rsdp)}); + return signature() == "RSD PTR " && validate_checksum({reinterpret_cast(this), sizeof(rsdp)}); } auto xsdp::length() const noexcept -> kstd::units::bytes @@ -49,7 +49,7 @@ namespace acpi auto xsdp::validate() const noexcept -> bool { - return validate_checksum({reinterpret_cast(this), m_length}); + return signature() == "RSD PTR " && validate_checksum({reinterpret_cast(this), m_length}); } } // namespace acpi diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp new file mode 100644 index 0000000..06ce1a4 --- /dev/null +++ b/libs/acpi/acpi/pointers.test.cpp @@ -0,0 +1,29 @@ +#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()); + } + } + } +} -- cgit v1.2.3 From 1fa31688a0e237dec1170dcea9e8f0a0571a25e5 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 15 Apr 2026 08:55:03 +0200 Subject: acpi: add basic MADT tests --- libs/acpi/CMakeLists.txt | 34 +++++++++++++++++++++++ libs/acpi/acpi/madt.cpp | 10 +++++-- libs/acpi/acpi/madt.hpp | 38 +++++++++++++++++++++++--- libs/acpi/acpi/madt.test.cpp | 55 ++++++++++++++++++++++++++++++++++++++ libs/acpi/acpi/sdt.cpp | 7 +++++ libs/acpi/acpi/sdt.hpp | 8 ++++++ libs/acpi/test_data/basic_madt.asl | 29 ++++++++++++++++++++ libs/acpi/test_data/basic_rsdt.asl | 26 ++++++++++++++++++ libs/acpi/test_data/tables.S | 14 ++++++++++ libs/acpi/test_data/tables.hpp | 25 +++++++++++++++++ 10 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 libs/acpi/acpi/madt.test.cpp create mode 100644 libs/acpi/test_data/basic_madt.asl create mode 100644 libs/acpi/test_data/basic_rsdt.asl create mode 100644 libs/acpi/test_data/tables.S create mode 100644 libs/acpi/test_data/tables.hpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index b8face4..8ace42d 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -1,3 +1,11 @@ +cmake_minimum_required(VERSION "3.27.0") + +project("acpi" + DESCRIPTION "An ACPI parsing library" + VERSION "0.0.1" + LANGUAGES ASM CXX +) + add_library("acpi" STATIC) add_library("libs::acpi" ALIAS "acpi") @@ -29,8 +37,34 @@ target_link_libraries("acpi" PUBLIC ) if(NOT CMAKE_CROSSCOMPILING) + find_program(IASL_EXE NAMES "iasl" REQUIRED) + + set(TEST_TABLES + "basic_madt" + "basic_rsdt" + ) + + foreach(TABLE IN LISTS TEST_TABLES) + add_custom_command(OUTPUT "test_data/${TABLE}.aml" + COMMAND "${IASL_EXE}" -p "test_data/${TABLE}.aml" "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" + COMMENT "Compiling test_data/${TABLE}.asl" + VERBATIM + ) + list(APPEND GENERATED_TABLE_BLOBS "${CMAKE_CURRENT_BINARY_DIR}/test_data/${TABLE}.aml") + endforeach() + + set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") + add_executable("acpi_tests" + "acpi/madt.test.cpp" "acpi/pointers.test.cpp" + + "test_data/tables.S" + ) + + target_include_directories("acpi_tests" PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/test_data" ) target_link_libraries("acpi_tests" PRIVATE diff --git a/libs/acpi/acpi/madt.cpp b/libs/acpi/acpi/madt.cpp index 40289cf..6a62f07 100644 --- a/libs/acpi/acpi/madt.cpp +++ b/libs/acpi/acpi/madt.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -16,9 +17,14 @@ namespace acpi return m_flags; } - auto madt_entry::type() const noexcept -> types + auto madt::validate() const noexcept -> bool { - return static_cast(m_type); + return signature() == madt_table_signature && sdt::validate(); + } + + auto madt_entry::type() const noexcept -> enum type + { + return static_cast(m_type); } auto madt_entry::length() const noexcept -> std::size_t diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp index f680430..8b75f58 100644 --- a/libs/acpi/acpi/madt.hpp +++ b/libs/acpi/acpi/madt.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include namespace acpi @@ -18,7 +20,8 @@ namespace acpi struct [[gnu::packed]] madt_entry { - enum struct types : std::uint8_t + //! The type of an MADT entry. + enum struct type : std::uint8_t { processor_local_apic, io_apic, @@ -29,28 +32,55 @@ namespace acpi processor_local_x2_apic, }; - [[nodiscard]] auto type() const noexcept -> types; + //! Get the type of this entry. + [[nodiscard]] auto type() const noexcept -> enum type; + + //! Get the length of this entry. [[nodiscard]] auto length() const noexcept -> std::size_t; + //! Cast this entry to the given concrete entry type. + //! + //! @warning This function will terminate execution if the desired target type does not match up with this entry's + //! actual type. + template + [[nodiscard]] auto as() const -> EntryType const & + { + if (type() != EntryType::this_type) + { + kstd::os::panic("Invalid cast"); + } + return reinterpret_cast(*this); + } + private: std::uint8_t m_type; std::uint8_t m_length; }; - //! The common header for all + //! The Multiple APIC Description Table (MADT) struct [[gnu::packed]] madt : sdt { using iterator = sdt_iterator; using const_iterator = iterator; + //! Get the physical address of the local interrupt controllers described by this entry. [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; + [[nodiscard]] auto flags() const noexcept -> std::uint32_t; + [[nodiscard]] auto validate() const noexcept -> bool; [[nodiscard]] auto begin() const noexcept -> iterator; [[nodiscard]] auto cbegin() const noexcept -> iterator; [[nodiscard]] auto end() const noexcept -> iterator; [[nodiscard]] auto cend() const noexcept -> iterator; + template + [[nodiscard]] auto only() const noexcept + { + return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) | + std::views::transform([](auto const & e) { return e.template as(); }); + } + private: std::uint32_t m_local_interrupt_controller_address; std::uint32_t m_flags; @@ -58,6 +88,8 @@ namespace acpi struct [[gnu::packed]] processor_local_apic : madt_entry { + constexpr auto static this_type = type::processor_local_apic; + enum struct flags : std::uint32_t { processor_enabled = 1, diff --git a/libs/acpi/acpi/madt.test.cpp b/libs/acpi/acpi/madt.test.cpp new file mode 100644 index 0000000..8926192 --- /dev/null +++ b/libs/acpi/acpi/madt.test.cpp @@ -0,0 +1,55 @@ +#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() == kstd::type_size + kstd::type_size); + } + + 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/sdt.cpp b/libs/acpi/acpi/sdt.cpp index c2b9d68..6c6cb47 100644 --- a/libs/acpi/acpi/sdt.cpp +++ b/libs/acpi/acpi/sdt.cpp @@ -1,7 +1,9 @@ #include +#include #include +#include #include #include @@ -48,4 +50,9 @@ namespace acpi return {m_signature.data(), m_signature.size()}; } + auto sdt::validate() const noexcept -> bool + { + return acpi::validate_checksum({reinterpret_cast(this), length().value}); + } + } // namespace acpi diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp index 9e5ec89..72b3896 100644 --- a/libs/acpi/acpi/sdt.hpp +++ b/libs/acpi/acpi/sdt.hpp @@ -52,6 +52,11 @@ namespace acpi return *m_entry; } + constexpr auto operator->() const noexcept -> pointer + { + return m_entry; + } + constexpr auto operator==(sdt_iterator const & other) const noexcept -> bool { return m_entry == other.m_entry || (is_end() && other.is_end()); @@ -94,6 +99,9 @@ namespace acpi //! Get the signature of this table. [[nodiscard]] auto signature() const noexcept -> std::string_view; + //! Valide this table's checksum + [[nodiscard]] auto validate() const noexcept -> bool; + private: std::array m_signature; std::uint32_t m_length; diff --git a/libs/acpi/test_data/basic_madt.asl b/libs/acpi/test_data/basic_madt.asl new file mode 100644 index 0000000..cd6958a --- /dev/null +++ b/libs/acpi/test_data/basic_madt.asl @@ -0,0 +1,29 @@ +/* + * Intel ACPI Component Architecture + * iASL Compiler/Disassembler version 20251212 (64-bit version) + * Copyright (c) 2000 - 2025 Intel Corporation + * + * Template for [APIC] ACPI Table (static data table) + * Format: [ByteLength] FieldName : HexFieldValue + */ +[0004] Signature : "APIC" [Multiple APIC Description Table (MADT)] +[0004] Table Length : 00000000 +[0001] Revision : 07 +[0001] Checksum : 00 +[0006] Oem ID : "INTEL " +[0008] Oem Table ID : "Template" +[0004] Oem Revision : 00000001 +[0004] Asl Compiler ID : "INTL" +[0004] Asl Compiler Revision : 20230628 + +[0004] Local Apic Address : FEE00000 +[0004] Flags (decoded below) : 00000001 + PC-AT Compatibility : 1 + +[0001] Subtable Type : 00 [Processor Local APIC] +[0001] Length : 08 +[0001] Processor ID : 00 +[0001] Local Apic ID : 00 +[0004] Flags (decoded below) : 00000001 + Processor Enabled : 1 + Runtime Online Capable : 0 diff --git a/libs/acpi/test_data/basic_rsdt.asl b/libs/acpi/test_data/basic_rsdt.asl new file mode 100644 index 0000000..6cf4c7a --- /dev/null +++ b/libs/acpi/test_data/basic_rsdt.asl @@ -0,0 +1,26 @@ +/* + * Intel ACPI Component Architecture + * iASL Compiler/Disassembler version 20251212 (64-bit version) + * Copyright (c) 2000 - 2025 Intel Corporation + * + * Template for [RSDT] ACPI Table (static data table) + * Format: [ByteLength] FieldName : HexFieldValue + */ +[0004] Signature : "RSDT" [Root System Description Table] +[0004] Table Length : 00000000 +[0001] Revision : 01 +[0001] Checksum : 00 +[0006] Oem ID : "INTEL " +[0008] Oem Table ID : "TEMPLATE" +[0004] Oem Revision : 00000001 +[0004] Asl Compiler ID : "INTL" +[0004] Asl Compiler Revision : 20100528 + +[0004] ACPI Table Address 0 : 00000010 +[0004] ACPI Table Address 1 : 00000020 +[0004] ACPI Table Address 2 : 00000030 +[0004] ACPI Table Address 3 : 00000040 +[0004] ACPI Table Address 4 : 00000050 +[0004] ACPI Table Address 5 : 00000060 +[0004] ACPI Table Address 6 : 00000070 +[0004] ACPI Table Address 7 : 00000080 diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S new file mode 100644 index 0000000..af58109 --- /dev/null +++ b/libs/acpi/test_data/tables.S @@ -0,0 +1,14 @@ +.section .rodata + +.balign 16 + +#define TABLE(name, file) \ + .global name##_start; \ + .global name##_end; \ + name##_start: .incbin file; \ + name##_end: + +TABLE(basic_madt, "basic_madt.aml") +TABLE(basic_rsdt, "basic_rsdt.aml") + +#undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp new file mode 100644 index 0000000..2b3874f --- /dev/null +++ b/libs/acpi/test_data/tables.hpp @@ -0,0 +1,25 @@ +#ifndef ACPI_TEST_DATA_TABLES_HPP +#define ACPI_TEST_DATA_TABLES_HPP + +#include +#include + +#define TABLE(name) \ + extern "C" std::byte const name##_start; \ + extern "C" std::byte const name##_end; \ + auto inline name()->std::span \ + { \ + return {&name##_start, &name##_end}; \ + } + +namespace acpi::test_data::tables +{ + + TABLE(basic_madt); + TABLE(basic_rsdt); + +} // namespace acpi::test_data::tables + +#undef TABLE + +#endif -- cgit v1.2.3 From 1113e812359a66591b0854a9f723ab8cd8b09274 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 15 Apr 2026 09:04:35 +0200 Subject: acpi: add rsdp test data --- libs/acpi/CMakeLists.txt | 1 + libs/acpi/test_data/basic_rsdp.asl | 16 ++++++++++++++++ libs/acpi/test_data/tables.S | 1 + libs/acpi/test_data/tables.hpp | 1 + 4 files changed, 19 insertions(+) create mode 100644 libs/acpi/test_data/basic_rsdp.asl (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 8ace42d..30e1aca 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -42,6 +42,7 @@ if(NOT CMAKE_CROSSCOMPILING) set(TEST_TABLES "basic_madt" "basic_rsdt" + "basic_rsdp" ) foreach(TABLE IN LISTS TEST_TABLES) diff --git a/libs/acpi/test_data/basic_rsdp.asl b/libs/acpi/test_data/basic_rsdp.asl new file mode 100644 index 0000000..8274c0f --- /dev/null +++ b/libs/acpi/test_data/basic_rsdp.asl @@ -0,0 +1,16 @@ +/* + * Intel ACPI Component Architecture + * iASL Compiler/Disassembler version 20251212 (64-bit version) + * Copyright (c) 2000 - 2025 Intel Corporation + * + * Template for [RSDP] ACPI Table (AML byte code table) + */ +[0008] Signature : "RSD PTR " +[0001] Checksum : 43 +[0006] Oem ID : "INTEL " +[0001] Revision : 02 +[0004] RSDT Address : 00000000 +[0004] Length : 00000024 +[0008] XSDT Address : 0000000000000000 +[0001] Extended Checksum : DC +[0003] Reserved : 000000 diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S index af58109..f40f070 100644 --- a/libs/acpi/test_data/tables.S +++ b/libs/acpi/test_data/tables.S @@ -10,5 +10,6 @@ TABLE(basic_madt, "basic_madt.aml") TABLE(basic_rsdt, "basic_rsdt.aml") +TABLE(basic_rsdp, "basic_rsdp.aml") #undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp index 2b3874f..510cbda 100644 --- a/libs/acpi/test_data/tables.hpp +++ b/libs/acpi/test_data/tables.hpp @@ -17,6 +17,7 @@ namespace acpi::test_data::tables TABLE(basic_madt); TABLE(basic_rsdt); + TABLE(basic_rsdp); } // namespace acpi::test_data::tables -- cgit v1.2.3 From 6344a2a81b94a00aaaa987d0e0d40993ed581d5e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 15 Apr 2026 16:19:00 +0200 Subject: acpi: reimplement the common table header --- libs/acpi/CMakeLists.txt | 3 + libs/acpi/acpi/common/table_header.cpp | 130 ++++++++++++++++++++++++++++ libs/acpi/acpi/common/table_header.hpp | 57 ++++++++++++ libs/acpi/acpi/common/table_header.test.cpp | 58 +++++++++++++ libs/acpi/test_data/table_header.asl | 9 ++ libs/acpi/test_data/tables.S | 1 + libs/acpi/test_data/tables.hpp | 1 + 7 files changed, 259 insertions(+) create mode 100644 libs/acpi/acpi/common/table_header.cpp create mode 100644 libs/acpi/acpi/common/table_header.hpp create mode 100644 libs/acpi/acpi/common/table_header.test.cpp create mode 100644 libs/acpi/test_data/table_header.asl (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 30e1aca..b9c607d 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -20,6 +20,7 @@ file(GLOB_RECURSE ACPI_HEADERS target_sources("acpi" PRIVATE "acpi/checksum.cpp" + "acpi/common/table_header.cpp" "acpi/madt.cpp" "acpi/pointers.cpp" "acpi/sdt.cpp" @@ -43,6 +44,7 @@ if(NOT CMAKE_CROSSCOMPILING) "basic_madt" "basic_rsdt" "basic_rsdp" + "table_header" ) foreach(TABLE IN LISTS TEST_TABLES) @@ -58,6 +60,7 @@ if(NOT CMAKE_CROSSCOMPILING) set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") add_executable("acpi_tests" + "acpi/common/table_header.test.cpp" "acpi/madt.test.cpp" "acpi/pointers.test.cpp" diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp new file mode 100644 index 0000000..17a8219 --- /dev/null +++ b/libs/acpi/acpi/common/table_header.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace acpi +{ + + struct common_table_header_data + { + std::array signature; + std::uint32_t length; + std::uint8_t revision; + std::uint8_t checksum; + std::array oem_id; + std::array oem_table_id; + std::uint32_t oem_revision; + std::array creator_id; + std::uint32_t creator_revision; + }; + + static_assert(sizeof(common_table_header_data) == common_table_header_size); + + auto common_table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision) + { + using type = decltype(common_table_header_data::creator_revision); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(common_table_header_data, creator_revision); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; + } + + auto common_table_header::creator_id() const noexcept -> std::string_view + { + constexpr auto size = sizeof(common_table_header_data::creator_id); + constexpr auto offset = offsetof(common_table_header_data, creator_id); + + auto const base = reinterpret_cast(m_data.data()); + + return std::string_view{base + offset, size}; + } + + auto common_table_header::length() const noexcept -> kstd::units::bytes + { + using type = decltype(common_table_header_data::length); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(common_table_header_data, length); + + auto const data = std::span{m_data.data() + offset, size}; + auto raw_value = type{}; + + kstd::libc::memcpy(&raw_value, data.data(), size); + + return kstd::units::bytes{raw_value}; + } + + auto common_table_header::oem_id() const noexcept -> std::string_view + { + constexpr auto size = sizeof(common_table_header_data::oem_id); + constexpr auto offset = offsetof(common_table_header_data, oem_id); + + auto const base = reinterpret_cast(m_data.data()); + + return std::string_view{base + offset, size}; + } + + auto common_table_header::oem_table_id() const noexcept -> std::string_view + { + constexpr auto size = sizeof(common_table_header_data::oem_table_id); + constexpr auto offset = offsetof(common_table_header_data, oem_table_id); + + auto const base = reinterpret_cast(m_data.data()); + + return std::string_view{base + offset, size}; + } + + auto common_table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision) + { + using type = decltype(common_table_header_data::oem_revision); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(common_table_header_data, oem_revision); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; + } + + auto common_table_header::revision() const noexcept -> decltype(common_table_header_data::revision) + { + using type = decltype(common_table_header_data::revision); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(common_table_header_data, revision); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; + } + + auto common_table_header::signature() const noexcept -> std::string_view + { + constexpr auto size = sizeof(common_table_header_data::signature); + constexpr auto offset = offsetof(common_table_header_data, signature); + + auto const base = reinterpret_cast(m_data.data()); + + return std::string_view{base + offset, size}; + } + +} // namespace acpi \ No newline at end of file diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp new file mode 100644 index 0000000..8ecfd0a --- /dev/null +++ b/libs/acpi/acpi/common/table_header.hpp @@ -0,0 +1,57 @@ +#ifndef ACPI_COMMON_TABLE_HEADER_HPP +#define ACPI_COMMON_TABLE_HEADER_HPP + +#include + +#include +#include +#include +#include + +namespace acpi +{ + + //! The size of the common table header as defined in the ACPI specification. + constexpr auto common_table_header_size = 36; + + //! The common header for all ACPI tables, except the FACS. + //! + //! Multibyte fields in the header are not guaranteed to be naturally aligned by the firmware. Therefore, no direct + //! access to multibyte fields is provided. Instead, the provided member functions handle alignment of the multibyte + //! values and thus provide a safe interface to the header fields. + struct common_table_header + { + //! Get the revision of the utility used to create this table. + [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t; + + //! Get the vendor ID of the utility used to create this table. + [[nodiscard]] auto creator_id() const noexcept -> std::string_view; + + //! Get the length of the entire table, including this header. + [[nodiscard]] auto length() const noexcept -> kstd::units::bytes; + + //! Get the ID of the OEM. + [[nodiscard]] auto oem_id() const noexcept -> std::string_view; + + //! Get the OEMs revision number of this table. + [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t; + + //! Get the OEMs ID of this table. + [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view; + + //! Get the revision number of the structure of this table. + [[nodiscard]] auto revision() const noexcept -> std::uint8_t; + + //! Get the signature of this table. + [[nodiscard]] auto signature() const noexcept -> std::string_view; + + //! Validate this table's checksum + [[nodiscard]] auto validate() const noexcept -> bool; + + private: + std::array m_data; + }; + +} // namespace acpi + +#endif \ No newline at end of file diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp new file mode 100644 index 0000000..e43e403 --- /dev/null +++ b/libs/acpi/acpi/common/table_header.test.cpp @@ -0,0 +1,58 @@ +#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/test_data/table_header.asl b/libs/acpi/test_data/table_header.asl new file mode 100644 index 0000000..8cddc03 --- /dev/null +++ b/libs/acpi/test_data/table_header.asl @@ -0,0 +1,9 @@ +[0004] Signature : "TEST" +[0004] Table Length : 00000000 +[0001] Revision : 01 +[0001] Checksum : 00 +[0006] Oem ID : "FEMO " +[0008] Oem Table ID : "HDRTEST " +[0004] Oem Revision : 00000001 +[0004] Asl Compiler ID : "INTL" +[0004] Asl Compiler Revision : 00000000 diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S index f40f070..da9a12f 100644 --- a/libs/acpi/test_data/tables.S +++ b/libs/acpi/test_data/tables.S @@ -11,5 +11,6 @@ TABLE(basic_madt, "basic_madt.aml") TABLE(basic_rsdt, "basic_rsdt.aml") TABLE(basic_rsdp, "basic_rsdp.aml") +TABLE(table_header, "table_header.aml") #undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp index 510cbda..dc39b37 100644 --- a/libs/acpi/test_data/tables.hpp +++ b/libs/acpi/test_data/tables.hpp @@ -18,6 +18,7 @@ namespace acpi::test_data::tables TABLE(basic_madt); TABLE(basic_rsdt); TABLE(basic_rsdp); + TABLE(table_header); } // namespace acpi::test_data::tables -- cgit v1.2.3 From 27c654f3f0a069113b6abb70817cfe2c5096711e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 15 Apr 2026 20:46:29 +0200 Subject: acpi: add basic table type --- libs/acpi/CMakeLists.txt | 4 +++- libs/acpi/acpi/acpi.hpp | 10 +++++----- libs/acpi/acpi/checksum.cpp | 19 ------------------- libs/acpi/acpi/checksum.hpp | 20 -------------------- libs/acpi/acpi/common/basic_table.cpp | 26 ++++++++++++++++++++++++++ libs/acpi/acpi/common/basic_table.hpp | 26 ++++++++++++++++++++++++++ libs/acpi/acpi/common/basic_table.test.cpp | 21 +++++++++++++++++++++ libs/acpi/acpi/common/checksum.cpp | 19 +++++++++++++++++++ libs/acpi/acpi/common/checksum.hpp | 20 ++++++++++++++++++++ libs/acpi/acpi/common/table_header.cpp | 16 ++++++++-------- libs/acpi/acpi/common/table_header.hpp | 5 +---- libs/acpi/acpi/common/table_header.test.cpp | 4 ++-- libs/acpi/acpi/pointers.cpp | 2 +- 13 files changed, 132 insertions(+), 60 deletions(-) delete mode 100644 libs/acpi/acpi/checksum.cpp delete mode 100644 libs/acpi/acpi/checksum.hpp create mode 100644 libs/acpi/acpi/common/basic_table.cpp create mode 100644 libs/acpi/acpi/common/basic_table.hpp create mode 100644 libs/acpi/acpi/common/basic_table.test.cpp create mode 100644 libs/acpi/acpi/common/checksum.cpp create mode 100644 libs/acpi/acpi/common/checksum.hpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index b9c607d..f850fe4 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -19,7 +19,8 @@ file(GLOB_RECURSE ACPI_HEADERS ) target_sources("acpi" PRIVATE - "acpi/checksum.cpp" + "acpi/common/basic_table.cpp" + "acpi/common/checksum.cpp" "acpi/common/table_header.cpp" "acpi/madt.cpp" "acpi/pointers.cpp" @@ -60,6 +61,7 @@ if(NOT CMAKE_CROSSCOMPILING) set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") add_executable("acpi_tests" + "acpi/common/basic_table.test.cpp" "acpi/common/table_header.test.cpp" "acpi/madt.test.cpp" "acpi/pointers.test.cpp" diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp index fb358cc..47050f2 100644 --- a/libs/acpi/acpi/acpi.hpp +++ b/libs/acpi/acpi/acpi.hpp @@ -1,10 +1,10 @@ #ifndef ACPI_ACPI_HPP #define ACPI_ACPI_HPP -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #endif diff --git a/libs/acpi/acpi/checksum.cpp b/libs/acpi/acpi/checksum.cpp deleted file mode 100644 index 56275c8..0000000 --- a/libs/acpi/acpi/checksum.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include -#include -#include -#include - -namespace acpi -{ - - auto validate_checksum(std::span data) -> bool - { - auto sum = std::ranges::fold_left(data, std::uint8_t{}, [](auto acc, auto byte) { - return static_cast(acc + static_cast(byte)); - }); - return sum == 0; - } - -} // namespace acpi diff --git a/libs/acpi/acpi/checksum.hpp b/libs/acpi/acpi/checksum.hpp deleted file mode 100644 index a92c242..0000000 --- a/libs/acpi/acpi/checksum.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ACPI_CHECKSUM_HPP -#define ACPI_CHECKSUM_HPP - -// IWYU pragma: private, include - -#include -#include - -namespace acpi -{ - - //! Validate and ACPI entity checksum. - //! - //! @param data The data to validate the checksum of. - //! @return true iff. the checksum is valid, false otherwise. - auto validate_checksum(std::span data) -> bool; - -} // namespace acpi - -#endif diff --git a/libs/acpi/acpi/common/basic_table.cpp b/libs/acpi/acpi/common/basic_table.cpp new file mode 100644 index 0000000..5cec91a --- /dev/null +++ b/libs/acpi/acpi/common/basic_table.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include +#include + +namespace acpi +{ + + auto basic_table::validate_checksum() const noexcept -> bool + { + return acpi::validate_checksum({reinterpret_cast(this), m_header.length().value}); + } + + auto basic_table::as_span() const noexcept -> std::span + { + return {reinterpret_cast(this), m_header.length().value}; + } + + auto basic_table::header() const noexcept -> table_header const & + { + return m_header; + } + +} // namespace acpi \ No newline at end of file diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp new file mode 100644 index 0000000..43bf533 --- /dev/null +++ b/libs/acpi/acpi/common/basic_table.hpp @@ -0,0 +1,26 @@ +#ifndef ACPI_COMMON_BASIC_TABLE_HPP +#define ACPI_COMMON_BASIC_TABLE_HPP + +#include + +#include +#include + +namespace acpi +{ + + struct basic_table + { + [[nodiscard]] auto validate_checksum() const noexcept -> bool; + + [[nodiscard]] auto as_span() const noexcept -> std::span; + + [[nodiscard]] auto header() const noexcept -> table_header const &; + + private: + table_header m_header; + }; + +} // namespace acpi + +#endif \ No newline at end of file diff --git a/libs/acpi/acpi/common/basic_table.test.cpp b/libs/acpi/acpi/common/basic_table.test.cpp new file mode 100644 index 0000000..e292b9f --- /dev/null +++ b/libs/acpi/acpi/common/basic_table.test.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +SCENARIO("Basic table functions", "[basic_table]") +{ + GIVEN("A valid compiled table header") + { + auto data = acpi::test_data::tables::table_header(); + + WHEN("parsing the table") + { + auto table = reinterpret_cast(data.data()); + + THEN("the checksum is valid") + { + REQUIRE(table->validate_checksum()); + } + } + } +} diff --git a/libs/acpi/acpi/common/checksum.cpp b/libs/acpi/acpi/common/checksum.cpp new file mode 100644 index 0000000..09425dd --- /dev/null +++ b/libs/acpi/acpi/common/checksum.cpp @@ -0,0 +1,19 @@ +#include + +#include +#include +#include +#include + +namespace acpi +{ + + auto validate_checksum(std::span data) -> bool + { + auto sum = std::ranges::fold_left(data, std::uint8_t{}, [](auto acc, auto byte) { + return static_cast(acc + static_cast(byte)); + }); + return sum == 0; + } + +} // namespace acpi diff --git a/libs/acpi/acpi/common/checksum.hpp b/libs/acpi/acpi/common/checksum.hpp new file mode 100644 index 0000000..a92c242 --- /dev/null +++ b/libs/acpi/acpi/common/checksum.hpp @@ -0,0 +1,20 @@ +#ifndef ACPI_CHECKSUM_HPP +#define ACPI_CHECKSUM_HPP + +// IWYU pragma: private, include + +#include +#include + +namespace acpi +{ + + //! Validate and ACPI entity checksum. + //! + //! @param data The data to validate the checksum of. + //! @return true iff. the checksum is valid, false otherwise. + auto validate_checksum(std::span data) -> bool; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp index 17a8219..c69ff5d 100644 --- a/libs/acpi/acpi/common/table_header.cpp +++ b/libs/acpi/acpi/common/table_header.cpp @@ -27,7 +27,7 @@ namespace acpi static_assert(sizeof(common_table_header_data) == common_table_header_size); - auto common_table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision) + auto table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision) { using type = decltype(common_table_header_data::creator_revision); @@ -42,7 +42,7 @@ namespace acpi return value; } - auto common_table_header::creator_id() const noexcept -> std::string_view + auto table_header::creator_id() const noexcept -> std::string_view { constexpr auto size = sizeof(common_table_header_data::creator_id); constexpr auto offset = offsetof(common_table_header_data, creator_id); @@ -52,7 +52,7 @@ namespace acpi return std::string_view{base + offset, size}; } - auto common_table_header::length() const noexcept -> kstd::units::bytes + auto table_header::length() const noexcept -> kstd::units::bytes { using type = decltype(common_table_header_data::length); @@ -67,7 +67,7 @@ namespace acpi return kstd::units::bytes{raw_value}; } - auto common_table_header::oem_id() const noexcept -> std::string_view + auto table_header::oem_id() const noexcept -> std::string_view { constexpr auto size = sizeof(common_table_header_data::oem_id); constexpr auto offset = offsetof(common_table_header_data, oem_id); @@ -77,7 +77,7 @@ namespace acpi return std::string_view{base + offset, size}; } - auto common_table_header::oem_table_id() const noexcept -> std::string_view + auto table_header::oem_table_id() const noexcept -> std::string_view { constexpr auto size = sizeof(common_table_header_data::oem_table_id); constexpr auto offset = offsetof(common_table_header_data, oem_table_id); @@ -87,7 +87,7 @@ namespace acpi return std::string_view{base + offset, size}; } - auto common_table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision) + auto table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision) { using type = decltype(common_table_header_data::oem_revision); @@ -102,7 +102,7 @@ namespace acpi return value; } - auto common_table_header::revision() const noexcept -> decltype(common_table_header_data::revision) + auto table_header::revision() const noexcept -> decltype(common_table_header_data::revision) { using type = decltype(common_table_header_data::revision); @@ -117,7 +117,7 @@ namespace acpi return value; } - auto common_table_header::signature() const noexcept -> std::string_view + auto table_header::signature() const noexcept -> std::string_view { constexpr auto size = sizeof(common_table_header_data::signature); constexpr auto offset = offsetof(common_table_header_data, signature); diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp index 8ecfd0a..529da81 100644 --- a/libs/acpi/acpi/common/table_header.hpp +++ b/libs/acpi/acpi/common/table_header.hpp @@ -19,7 +19,7 @@ namespace acpi //! Multibyte fields in the header are not guaranteed to be naturally aligned by the firmware. Therefore, no direct //! access to multibyte fields is provided. Instead, the provided member functions handle alignment of the multibyte //! values and thus provide a safe interface to the header fields. - struct common_table_header + struct table_header { //! Get the revision of the utility used to create this table. [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t; @@ -45,9 +45,6 @@ namespace acpi //! Get the signature of this table. [[nodiscard]] auto signature() const noexcept -> std::string_view; - //! Validate this table's checksum - [[nodiscard]] auto validate() const noexcept -> bool; - private: std::array m_data; }; diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp index e43e403..d6976b3 100644 --- a/libs/acpi/acpi/common/table_header.test.cpp +++ b/libs/acpi/acpi/common/table_header.test.cpp @@ -12,7 +12,7 @@ SCENARIO("Common table header parsing", "[common_table_header]") WHEN("parsing the header") { - auto header = reinterpret_cast(data.data()); + auto header = reinterpret_cast(data.data()); THEN("the signature is correct") { @@ -26,7 +26,7 @@ SCENARIO("Common table header parsing", "[common_table_header]") THEN("the length is correct") { - REQUIRE(header->length() == kstd::type_size); + REQUIRE(header->length() == kstd::type_size); } THEN("the oem id is correct") diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp index 8a8629f..45a42ce 100644 --- a/libs/acpi/acpi/pointers.cpp +++ b/libs/acpi/acpi/pointers.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include -- cgit v1.2.3 From 252df23b061b2bf54206da73e41faca600541ccc Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 09:26:52 +0200 Subject: acpi: introduce VLA table --- libs/acpi/CMakeLists.txt | 3 - libs/acpi/acpi/acpi.hpp | 10 +-- libs/acpi/acpi/common/basic_table.cpp | 26 ------ libs/acpi/acpi/common/basic_table.hpp | 38 +++++++-- libs/acpi/acpi/common/basic_table.test.cpp | 21 ----- libs/acpi/acpi/common/table_header.hpp | 2 + libs/acpi/acpi/common/table_signature.hpp | 17 ++++ libs/acpi/acpi/common/table_type.hpp | 17 ++++ libs/acpi/acpi/common/vla_table.hpp | 115 ++++++++++++++++++++++++++ libs/acpi/acpi/madt.cpp | 104 +++++++++++++++-------- libs/acpi/acpi/madt.hpp | 57 ++++++------- libs/acpi/acpi/madt.test.cpp | 4 +- libs/acpi/acpi/sdt.cpp | 58 ------------- libs/acpi/acpi/sdt.hpp | 127 ----------------------------- libs/acpi/acpi/table_type.hpp | 17 ---- 15 files changed, 287 insertions(+), 329 deletions(-) delete mode 100644 libs/acpi/acpi/common/basic_table.cpp delete mode 100644 libs/acpi/acpi/common/basic_table.test.cpp create mode 100644 libs/acpi/acpi/common/table_signature.hpp create mode 100644 libs/acpi/acpi/common/table_type.hpp create mode 100644 libs/acpi/acpi/common/vla_table.hpp delete mode 100644 libs/acpi/acpi/sdt.cpp delete mode 100644 libs/acpi/acpi/sdt.hpp delete mode 100644 libs/acpi/acpi/table_type.hpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index f850fe4..97c351b 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -19,12 +19,10 @@ file(GLOB_RECURSE ACPI_HEADERS ) target_sources("acpi" PRIVATE - "acpi/common/basic_table.cpp" "acpi/common/checksum.cpp" "acpi/common/table_header.cpp" "acpi/madt.cpp" "acpi/pointers.cpp" - "acpi/sdt.cpp" ) target_sources("acpi" PUBLIC @@ -61,7 +59,6 @@ if(NOT CMAKE_CROSSCOMPILING) set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") add_executable("acpi_tests" - "acpi/common/basic_table.test.cpp" "acpi/common/table_header.test.cpp" "acpi/madt.test.cpp" "acpi/pointers.test.cpp" diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp index 47050f2..2962c7a 100644 --- a/libs/acpi/acpi/acpi.hpp +++ b/libs/acpi/acpi/acpi.hpp @@ -1,10 +1,10 @@ #ifndef ACPI_ACPI_HPP #define ACPI_ACPI_HPP -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #endif diff --git a/libs/acpi/acpi/common/basic_table.cpp b/libs/acpi/acpi/common/basic_table.cpp deleted file mode 100644 index 5cec91a..0000000 --- a/libs/acpi/acpi/common/basic_table.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include - -#include -#include - -namespace acpi -{ - - auto basic_table::validate_checksum() const noexcept -> bool - { - return acpi::validate_checksum({reinterpret_cast(this), m_header.length().value}); - } - - auto basic_table::as_span() const noexcept -> std::span - { - return {reinterpret_cast(this), m_header.length().value}; - } - - auto basic_table::header() const noexcept -> table_header const & - { - return m_header; - } - -} // namespace acpi \ No newline at end of file diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp index 43bf533..b0ead96 100644 --- a/libs/acpi/acpi/common/basic_table.hpp +++ b/libs/acpi/acpi/common/basic_table.hpp @@ -1,21 +1,49 @@ #ifndef ACPI_COMMON_BASIC_TABLE_HPP #define ACPI_COMMON_BASIC_TABLE_HPP +#include #include +#include #include #include +#include namespace acpi { + template struct basic_table { - [[nodiscard]] auto validate_checksum() const noexcept -> bool; - - [[nodiscard]] auto as_span() const noexcept -> std::span; - - [[nodiscard]] auto header() const noexcept -> table_header const &; + [[nodiscard]] auto validate_checksum() const noexcept -> bool + { + return acpi::validate_checksum({reinterpret_cast(this), m_header.length().value}); + } + + [[nodiscard]] auto as_span() const noexcept -> std::span + { + return {reinterpret_cast(this), m_header.length().value}; + } + + [[nodiscard]] auto header() const noexcept -> table_header const & + { + return m_header; + } + + [[nodiscard]] auto length() const noexcept -> std::size_t + { + return m_header.length().value; + } + + [[nodiscard]] auto signature() const noexcept -> std::string_view + { + return m_header.signature(); + } + + [[nodiscard]] auto validate() const noexcept -> bool + { + return signature() == table_signature_v && validate_checksum(); + } private: table_header m_header; diff --git a/libs/acpi/acpi/common/basic_table.test.cpp b/libs/acpi/acpi/common/basic_table.test.cpp deleted file mode 100644 index e292b9f..0000000 --- a/libs/acpi/acpi/common/basic_table.test.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include - -SCENARIO("Basic table functions", "[basic_table]") -{ - GIVEN("A valid compiled table header") - { - auto data = acpi::test_data::tables::table_header(); - - WHEN("parsing the table") - { - auto table = reinterpret_cast(data.data()); - - THEN("the checksum is valid") - { - REQUIRE(table->validate_checksum()); - } - } - } -} diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp index 529da81..471fed8 100644 --- a/libs/acpi/acpi/common/table_header.hpp +++ b/libs/acpi/acpi/common/table_header.hpp @@ -1,6 +1,8 @@ #ifndef ACPI_COMMON_TABLE_HEADER_HPP #define ACPI_COMMON_TABLE_HEADER_HPP +// IWYU pragma: private, include + #include #include diff --git a/libs/acpi/acpi/common/table_signature.hpp b/libs/acpi/acpi/common/table_signature.hpp new file mode 100644 index 0000000..f0c2d7a --- /dev/null +++ b/libs/acpi/acpi/common/table_signature.hpp @@ -0,0 +1,17 @@ +#ifndef ACPI_COMMON_TABLE_SIGNATURE_HPP +#define ACPI_COMMON_TABLE_SIGNATURE_HPP + +// IWYU pragma: private, include + +namespace acpi +{ + + template + struct table_signature; + + template + constexpr auto table_signature_v = table_signature::value; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/common/table_type.hpp b/libs/acpi/acpi/common/table_type.hpp new file mode 100644 index 0000000..bc427c7 --- /dev/null +++ b/libs/acpi/acpi/common/table_type.hpp @@ -0,0 +1,17 @@ +#ifndef ACPI_COMMON_TABLE_TYPE_HPP +#define ACPI_COMMON_TABLE_TYPE_HPP + +// IWYU pragma: private, include + +namespace acpi +{ + + template + struct table_type; + + template + using table_type_t = typename table_type::type; + +} // namespace acpi + +#endif diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp new file mode 100644 index 0000000..450209c --- /dev/null +++ b/libs/acpi/acpi/common/vla_table.hpp @@ -0,0 +1,115 @@ +#ifndef ACPI_COMMON_VLA_TABLE_HPP +#define ACPI_COMMON_VLA_TABLE_HPP + +#include + +#include +#include + +namespace acpi +{ + + template + struct vla_table_iterator + { + using iterator_category = std::forward_iterator_tag; + using value_type = EntryType; + using pointer = value_type *; + using reference = value_type &; + using difference_type = std::ptrdiff_t; + + constexpr vla_table_iterator() noexcept = default; + + constexpr vla_table_iterator(pointer entry, pointer limit) noexcept + : m_entry{entry} + , m_limit{limit} + {} + + constexpr auto operator++() noexcept -> vla_table_iterator & + { + auto decayed = std::bit_cast(m_entry); + decayed += m_entry->length(); + m_entry = std::bit_cast(decayed); + return *this; + } + + constexpr auto operator++(int) noexcept -> vla_table_iterator + { + auto copy = *this; + ++*this; + return copy; + } + + constexpr auto operator*() const noexcept -> reference + { + return *m_entry; + } + + constexpr auto operator->() const noexcept -> pointer + { + return m_entry; + } + + constexpr auto operator==(vla_table_iterator const & other) const noexcept -> bool + { + return m_entry == other.m_entry || (is_end() && other.is_end()); + } + + private: + [[nodiscard]] constexpr auto is_end() const noexcept -> bool + { + return m_entry == m_limit; + } + + pointer m_entry{}; + pointer m_limit{}; + }; + + template + struct vla_table : basic_table + { + using entry_type = EntryType; + using iterator = vla_table_iterator; + using const_iterator = vla_table_iterator; + + [[nodiscard]] auto begin() noexcept -> iterator + { + auto base = std::bit_cast(this); + base += sizeof(TableType); + auto limit = std::bit_cast(this); + limit += this->length(); + return iterator{std::bit_cast(base), std::bit_cast(limit)}; + } + + [[nodiscard]] auto end() noexcept -> iterator + { + return iterator{}; + } + + [[nodiscard]] auto begin() const noexcept -> const_iterator + { + auto base = std::bit_cast(this); + base += sizeof(TableType); + auto limit = std::bit_cast(this); + limit += this->length(); + return const_iterator{std::bit_cast(base), std::bit_cast(limit)}; + } + + [[nodiscard]] auto end() const noexcept -> const_iterator + { + return const_iterator{}; + } + + [[nodiscard]] auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + [[nodiscard]] auto cend() const noexcept -> const_iterator + { + return end(); + } + }; +} // namespace acpi + +#endif \ No newline at end of file diff --git a/libs/acpi/acpi/madt.cpp b/libs/acpi/acpi/madt.cpp index 6a62f07..c6830ad 100644 --- a/libs/acpi/acpi/madt.cpp +++ b/libs/acpi/acpi/madt.cpp @@ -1,74 +1,112 @@ -#include +#include + #include +#include #include #include +#include namespace acpi { + struct madt_data + { + std::uint32_t local_interrupt_controller_address; + std::uint32_t flags; + }; + + static_assert(sizeof(madt_data) == 8); + auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t { - return static_cast(m_local_interrupt_controller_address); + using type = decltype(madt_data::local_interrupt_controller_address); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; } auto madt::flags() const noexcept -> std::uint32_t { - return m_flags; + using type = decltype(madt_data::flags); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(madt_data, flags); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; } - auto madt::validate() const noexcept -> bool + struct madt_entry_data { - return signature() == madt_table_signature && sdt::validate(); - } + std::uint8_t type; + std::uint8_t length; + }; + + static_assert(sizeof(madt_entry_data) == 2); auto madt_entry::type() const noexcept -> enum type { - return static_cast(m_type); + constexpr auto field_offset = offsetof(madt_entry_data, type); + + return std::bit_cast(*(m_data.data() + field_offset)); } auto madt_entry::length() const noexcept -> std::size_t { - return m_length; - } + constexpr auto field_offset = offsetof(madt_entry_data, length); - auto processor_local_apic::apic_id() const noexcept -> std::uint8_t - { - return m_apic_id; + return static_cast(*(m_data.data() + field_offset)); } - auto processor_local_apic::active_flags() const noexcept -> flags + struct [[gnu::packed]] processor_local_apic_entry_data { - return static_cast(m_flags); - } + std::uint8_t apic_id; + std::uint8_t processor_id; + std::uint32_t flags; + }; - auto processor_local_apic::processor_id() const noexcept -> std::uint32_t - { - return m_processor_id; - } + static_assert(sizeof(processor_local_apic_entry_data) == 6); - auto madt::begin() const noexcept -> iterator + auto processor_local_apic_entry::id() const noexcept -> std::uint8_t { - auto base = reinterpret_cast(this); - base += sizeof(madt); - auto limit = reinterpret_cast(this); - limit += length().value; - return iterator{reinterpret_cast(base), reinterpret_cast(limit)}; - } + constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id); - auto madt::cbegin() const noexcept -> iterator - { - return begin(); + return static_cast(*(m_data.data() + field_offset)); } - auto madt::end() const noexcept -> iterator + auto processor_local_apic_entry::flags() const noexcept -> enum flags { - return {}; + using type = decltype(processor_local_apic_entry_data::flags); + static_assert(sizeof(type) == sizeof(enum flags)); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(processor_local_apic_entry_data, flags); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return static_cast(value); } - auto madt::cend() const noexcept -> iterator + auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t { - return end(); + constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id); + + return static_cast(*(m_data.data() + field_offset)); } } // namespace acpi diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp index 8b75f58..8307826 100644 --- a/libs/acpi/acpi/madt.hpp +++ b/libs/acpi/acpi/madt.hpp @@ -7,9 +7,11 @@ #include #include -#include -#include +#include +#include +#include +#include #include #include #include @@ -18,7 +20,19 @@ namespace acpi { - struct [[gnu::packed]] madt_entry + template<> + struct table_signature + { + constexpr char static const value[] = "APIC"; // NOLINT + }; + + template<> + struct table_type> + { + using type = struct madt; + }; + + struct madt_entry { //! The type of an MADT entry. enum struct type : std::uint8_t @@ -53,26 +67,16 @@ namespace acpi } private: - std::uint8_t m_type; - std::uint8_t m_length; + std::array m_data{}; }; //! The Multiple APIC Description Table (MADT) - struct [[gnu::packed]] madt : sdt + struct madt : vla_table { - using iterator = sdt_iterator; - using const_iterator = iterator; - //! Get the physical address of the local interrupt controllers described by this entry. [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; [[nodiscard]] auto flags() const noexcept -> std::uint32_t; - [[nodiscard]] auto validate() const noexcept -> bool; - - [[nodiscard]] auto begin() const noexcept -> iterator; - [[nodiscard]] auto cbegin() const noexcept -> iterator; - [[nodiscard]] auto end() const noexcept -> iterator; - [[nodiscard]] auto cend() const noexcept -> iterator; template [[nodiscard]] auto only() const noexcept @@ -82,11 +86,10 @@ namespace acpi } private: - std::uint32_t m_local_interrupt_controller_address; - std::uint32_t m_flags; + std::array m_data{}; }; - struct [[gnu::packed]] processor_local_apic : madt_entry + struct processor_local_apic_entry : madt_entry { constexpr auto static this_type = type::processor_local_apic; @@ -96,28 +99,18 @@ namespace acpi online_capable = 2, }; - [[nodiscard]] auto apic_id() const noexcept -> std::uint8_t; - [[nodiscard]] auto active_flags() const noexcept -> flags; + [[nodiscard]] auto id() const noexcept -> std::uint8_t; + [[nodiscard]] auto flags() const noexcept -> flags; [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t; private: - std::uint8_t m_processor_id; - std::uint8_t m_apic_id; - std::uint32_t m_flags; - }; - - constexpr char const madt_table_signature[] = "APIC"; // NOLINT - - template<> - struct table_type - { - using type = madt; + std::array m_data{}; }; } // namespace acpi template<> -struct kstd::ext::is_bitfield_enum : std::true_type +struct kstd::ext::is_bitfield_enum : std::true_type { }; diff --git a/libs/acpi/acpi/madt.test.cpp b/libs/acpi/acpi/madt.test.cpp index 8926192..899a377 100644 --- a/libs/acpi/acpi/madt.test.cpp +++ b/libs/acpi/acpi/madt.test.cpp @@ -38,7 +38,7 @@ SCENARIO("MADT parsing", "[madt]") THEN("the length is sizeof(madt) + sizeof(processor_local_apic)") { - REQUIRE(madt->length() == kstd::type_size + kstd::type_size); + REQUIRE(madt->length() == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry)); } THEN("the first entry has type processor_local_apic") @@ -48,7 +48,7 @@ SCENARIO("MADT parsing", "[madt]") THEN("`only` can be used to get a view of all processor_local_apic entries") { - REQUIRE(std::ranges::distance(madt->only()) == 1); + REQUIRE(std::ranges::distance(madt->only()) == 1); } } } diff --git a/libs/acpi/acpi/sdt.cpp b/libs/acpi/acpi/sdt.cpp deleted file mode 100644 index 6c6cb47..0000000 --- a/libs/acpi/acpi/sdt.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include - -#include -#include - -#include -#include -#include - -namespace acpi -{ - - auto sdt::creator_revision() const noexcept -> std::uint32_t - { - return m_creator_revision; - } - - auto sdt::creator_id() const noexcept -> std::uint32_t - { - return m_creator_id; - } - - auto sdt::length() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{m_length}; - } - - auto sdt::oem_id() const noexcept -> std::string_view - { - return {m_oem_id.data(), m_oem_id.size()}; - } - - auto sdt::oem_revision() const noexcept -> std::uint32_t - { - return m_oem_revision; - } - - auto sdt::oem_table_id() const noexcept -> std::string_view - { - return {m_oem_table_id.data(), m_oem_table_id.size()}; - } - - auto sdt::revision() const noexcept -> std::uint8_t - { - return m_revision; - } - - auto sdt::signature() const noexcept -> std::string_view - { - return {m_signature.data(), m_signature.size()}; - } - - auto sdt::validate() const noexcept -> bool - { - return acpi::validate_checksum({reinterpret_cast(this), length().value}); - } - -} // namespace acpi diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp deleted file mode 100644 index 72b3896..0000000 --- a/libs/acpi/acpi/sdt.hpp +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef ACPI_SDT_HPP -#define ACPI_SDT_HPP - -// IWYU pragma: private, include - -#include - -#include - -#include -#include -#include -#include -#include - -namespace acpi -{ - - template - struct sdt_iterator - { - using iterator_category = std::forward_iterator_tag; - using value_type = EntryType; - using pointer = value_type *; - using reference = value_type &; - using difference_type = std::ptrdiff_t; - - constexpr sdt_iterator() noexcept = default; - - constexpr sdt_iterator(pointer entry, pointer limit) noexcept - : m_entry{entry} - , m_limit{limit} - {} - - constexpr auto operator++() noexcept -> sdt_iterator & - { - auto decayed = std::bit_cast(m_entry); - decayed += m_entry->length(); - m_entry = std::bit_cast(decayed); - return *this; - } - - constexpr auto operator++(int) noexcept -> sdt_iterator - { - auto copy = *this; - ++*this; - return copy; - } - - constexpr auto operator*() const noexcept -> reference - { - return *m_entry; - } - - constexpr auto operator->() const noexcept -> pointer - { - return m_entry; - } - - constexpr auto operator==(sdt_iterator const & other) const noexcept -> bool - { - return m_entry == other.m_entry || (is_end() && other.is_end()); - } - - private: - [[nodiscard]] constexpr auto is_end() const noexcept -> bool - { - return m_entry == m_limit; - } - - pointer m_entry{}; - pointer m_limit{}; - }; - - //! The common base of all System Description Tables. - struct [[gnu::packed]] sdt - { - //! Get the revision of the utility used to create this table. - [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t; - - //! Get the vendor ID of the utility used to create this table. - [[nodiscard]] auto creator_id() const noexcept -> std::uint32_t; - - //! Get the length of the entire table, including this header. - [[nodiscard]] auto length() const noexcept -> kstd::units::bytes; - - //! Get the ID of the OEM. - [[nodiscard]] auto oem_id() const noexcept -> std::string_view; - - //! Get the OEMs revision number of this table. - [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t; - - //! Get the OEMs ID of this table. - [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view; - - //! Get the revision number of the structure of this table. - [[nodiscard]] auto revision() const noexcept -> std::uint8_t; - - //! Get the signature of this table. - [[nodiscard]] auto signature() const noexcept -> std::string_view; - - //! Valide this table's checksum - [[nodiscard]] auto validate() const noexcept -> bool; - - private: - std::array m_signature; - std::uint32_t m_length; - std::uint8_t m_revision; - [[maybe_unused]] std::uint8_t m_checksum; - std::array m_oem_id; - std::array m_oem_table_id; - std::uint32_t m_oem_revision; - std::uint32_t m_creator_id; - std::uint32_t m_creator_revision; - }; - - constexpr char const rsdt_table_signature[] = "RSDT"; // NOLINT - - template<> - struct table_type - { - using type = sdt; - }; - -} // namespace acpi - -#endif diff --git a/libs/acpi/acpi/table_type.hpp b/libs/acpi/acpi/table_type.hpp deleted file mode 100644 index 7fdd7e3..0000000 --- a/libs/acpi/acpi/table_type.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef ACPI_TABLE_TYPE_HPP -#define ACPI_TABLE_TYPE_HPP - -// IWYU pragma: private, include - -namespace acpi -{ - - template - struct table_type; - - template - using table_type_t = typename table_type::type; - -} // namespace acpi - -#endif -- cgit v1.2.3 From 89f1c730fb9daf4a5da0748934ca5befd90eb731 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 09:28:16 +0200 Subject: acpi: move madt to data --- libs/acpi/CMakeLists.txt | 4 +- libs/acpi/acpi/acpi.hpp | 2 +- libs/acpi/acpi/data/madt.cpp | 112 ++++++++++++++++++++++++++++++++++++ libs/acpi/acpi/data/madt.hpp | 117 ++++++++++++++++++++++++++++++++++++++ libs/acpi/acpi/data/madt.test.cpp | 55 ++++++++++++++++++ libs/acpi/acpi/madt.cpp | 112 ------------------------------------ libs/acpi/acpi/madt.hpp | 117 -------------------------------------- libs/acpi/acpi/madt.test.cpp | 55 ------------------ 8 files changed, 287 insertions(+), 287 deletions(-) create mode 100644 libs/acpi/acpi/data/madt.cpp create mode 100644 libs/acpi/acpi/data/madt.hpp create mode 100644 libs/acpi/acpi/data/madt.test.cpp delete mode 100644 libs/acpi/acpi/madt.cpp delete mode 100644 libs/acpi/acpi/madt.hpp delete mode 100644 libs/acpi/acpi/madt.test.cpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 97c351b..f6e484c 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -21,7 +21,7 @@ file(GLOB_RECURSE ACPI_HEADERS target_sources("acpi" PRIVATE "acpi/common/checksum.cpp" "acpi/common/table_header.cpp" - "acpi/madt.cpp" + "acpi/data/madt.cpp" "acpi/pointers.cpp" ) @@ -60,7 +60,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_executable("acpi_tests" "acpi/common/table_header.test.cpp" - "acpi/madt.test.cpp" + "acpi/data/madt.test.cpp" "acpi/pointers.test.cpp" "test_data/tables.S" diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp index 2962c7a..4cfcede 100644 --- a/libs/acpi/acpi/acpi.hpp +++ b/libs/acpi/acpi/acpi.hpp @@ -4,7 +4,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export -#include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #endif diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp new file mode 100644 index 0000000..a8d4741 --- /dev/null +++ b/libs/acpi/acpi/data/madt.cpp @@ -0,0 +1,112 @@ +#include + +#include + +#include +#include +#include +#include + +namespace acpi +{ + + struct madt_data + { + std::uint32_t local_interrupt_controller_address; + std::uint32_t flags; + }; + + static_assert(sizeof(madt_data) == 8); + + auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t + { + using type = decltype(madt_data::local_interrupt_controller_address); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; + } + + auto madt::flags() const noexcept -> std::uint32_t + { + using type = decltype(madt_data::flags); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(madt_data, flags); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return value; + } + + struct madt_entry_data + { + std::uint8_t type; + std::uint8_t length; + }; + + static_assert(sizeof(madt_entry_data) == 2); + + auto madt_entry::type() const noexcept -> enum type + { + constexpr auto field_offset = offsetof(madt_entry_data, type); + + return std::bit_cast(*(m_data.data() + field_offset)); + } + + auto madt_entry::length() const noexcept -> std::size_t + { + constexpr auto field_offset = offsetof(madt_entry_data, length); + + return static_cast(*(m_data.data() + field_offset)); + } + + struct [[gnu::packed]] processor_local_apic_entry_data + { + std::uint8_t apic_id; + std::uint8_t processor_id; + std::uint32_t flags; + }; + + static_assert(sizeof(processor_local_apic_entry_data) == 6); + + auto processor_local_apic_entry::id() const noexcept -> std::uint8_t + { + constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id); + + return static_cast(*(m_data.data() + field_offset)); + } + + auto processor_local_apic_entry::flags() const noexcept -> enum flags + { + using type = decltype(processor_local_apic_entry_data::flags); + static_assert(sizeof(type) == sizeof(enum flags)); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(processor_local_apic_entry_data, flags); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return static_cast(value); + } + + auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t + { + constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id); + + return static_cast(*(m_data.data() + field_offset)); + } + +} // namespace acpi diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp new file mode 100644 index 0000000..8307826 --- /dev/null +++ b/libs/acpi/acpi/data/madt.hpp @@ -0,0 +1,117 @@ +#ifndef ACPI_ACPI_MADT_HPP +#define ACPI_ACPI_MADT_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace acpi +{ + + template<> + struct table_signature + { + constexpr char static const value[] = "APIC"; // NOLINT + }; + + template<> + struct table_type> + { + using type = struct madt; + }; + + struct madt_entry + { + //! The type of an MADT entry. + enum struct type : std::uint8_t + { + processor_local_apic, + io_apic, + io_apic_interrupt_source_override, + io_apic_nmi_source, + local_apic_nmi_interrupts, + local_apic_address_override, + processor_local_x2_apic, + }; + + //! Get the type of this entry. + [[nodiscard]] auto type() const noexcept -> enum type; + + //! Get the length of this entry. + [[nodiscard]] auto length() const noexcept -> std::size_t; + + //! Cast this entry to the given concrete entry type. + //! + //! @warning This function will terminate execution if the desired target type does not match up with this entry's + //! actual type. + template + [[nodiscard]] auto as() const -> EntryType const & + { + if (type() != EntryType::this_type) + { + kstd::os::panic("Invalid cast"); + } + return reinterpret_cast(*this); + } + + private: + std::array m_data{}; + }; + + //! The Multiple APIC Description Table (MADT) + struct madt : vla_table + { + //! Get the physical address of the local interrupt controllers described by this entry. + [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; + + [[nodiscard]] auto flags() const noexcept -> std::uint32_t; + + template + [[nodiscard]] auto only() const noexcept + { + return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) | + std::views::transform([](auto const & e) { return e.template as(); }); + } + + private: + std::array m_data{}; + }; + + struct processor_local_apic_entry : madt_entry + { + constexpr auto static this_type = type::processor_local_apic; + + enum struct flags : std::uint32_t + { + processor_enabled = 1, + online_capable = 2, + }; + + [[nodiscard]] auto id() const noexcept -> std::uint8_t; + [[nodiscard]] auto flags() const noexcept -> flags; + [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t; + + private: + std::array m_data{}; + }; + +} // namespace acpi + +template<> +struct kstd::ext::is_bitfield_enum : std::true_type +{ +}; + +#endif diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp new file mode 100644 index 0000000..2b74d4c --- /dev/null +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -0,0 +1,55 @@ +#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() == 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/madt.cpp b/libs/acpi/acpi/madt.cpp deleted file mode 100644 index c6830ad..0000000 --- a/libs/acpi/acpi/madt.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -namespace acpi -{ - - struct madt_data - { - std::uint32_t local_interrupt_controller_address; - std::uint32_t flags; - }; - - static_assert(sizeof(madt_data) == 8); - - auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t - { - using type = decltype(madt_data::local_interrupt_controller_address); - - constexpr auto size = sizeof(type); - constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address); - - auto const data = std::span{m_data.data() + offset, size}; - auto value = type{}; - - kstd::libc::memcpy(&value, data.data(), size); - - return value; - } - - auto madt::flags() const noexcept -> std::uint32_t - { - using type = decltype(madt_data::flags); - - constexpr auto size = sizeof(type); - constexpr auto offset = offsetof(madt_data, flags); - - auto const data = std::span{m_data.data() + offset, size}; - auto value = type{}; - - kstd::libc::memcpy(&value, data.data(), size); - - return value; - } - - struct madt_entry_data - { - std::uint8_t type; - std::uint8_t length; - }; - - static_assert(sizeof(madt_entry_data) == 2); - - auto madt_entry::type() const noexcept -> enum type - { - constexpr auto field_offset = offsetof(madt_entry_data, type); - - return std::bit_cast(*(m_data.data() + field_offset)); - } - - auto madt_entry::length() const noexcept -> std::size_t - { - constexpr auto field_offset = offsetof(madt_entry_data, length); - - return static_cast(*(m_data.data() + field_offset)); - } - - struct [[gnu::packed]] processor_local_apic_entry_data - { - std::uint8_t apic_id; - std::uint8_t processor_id; - std::uint32_t flags; - }; - - static_assert(sizeof(processor_local_apic_entry_data) == 6); - - auto processor_local_apic_entry::id() const noexcept -> std::uint8_t - { - constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id); - - return static_cast(*(m_data.data() + field_offset)); - } - - auto processor_local_apic_entry::flags() const noexcept -> enum flags - { - using type = decltype(processor_local_apic_entry_data::flags); - static_assert(sizeof(type) == sizeof(enum flags)); - - constexpr auto size = sizeof(type); - constexpr auto offset = offsetof(processor_local_apic_entry_data, flags); - - auto const data = std::span{m_data.data() + offset, size}; - auto value = type{}; - - kstd::libc::memcpy(&value, data.data(), size); - - return static_cast(value); - } - - auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t - { - constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id); - - return static_cast(*(m_data.data() + field_offset)); - } - -} // namespace acpi diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp deleted file mode 100644 index 8307826..0000000 --- a/libs/acpi/acpi/madt.hpp +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef ACPI_ACPI_MADT_HPP -#define ACPI_ACPI_MADT_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace acpi -{ - - template<> - struct table_signature - { - constexpr char static const value[] = "APIC"; // NOLINT - }; - - template<> - struct table_type> - { - using type = struct madt; - }; - - struct madt_entry - { - //! The type of an MADT entry. - enum struct type : std::uint8_t - { - processor_local_apic, - io_apic, - io_apic_interrupt_source_override, - io_apic_nmi_source, - local_apic_nmi_interrupts, - local_apic_address_override, - processor_local_x2_apic, - }; - - //! Get the type of this entry. - [[nodiscard]] auto type() const noexcept -> enum type; - - //! Get the length of this entry. - [[nodiscard]] auto length() const noexcept -> std::size_t; - - //! Cast this entry to the given concrete entry type. - //! - //! @warning This function will terminate execution if the desired target type does not match up with this entry's - //! actual type. - template - [[nodiscard]] auto as() const -> EntryType const & - { - if (type() != EntryType::this_type) - { - kstd::os::panic("Invalid cast"); - } - return reinterpret_cast(*this); - } - - private: - std::array m_data{}; - }; - - //! The Multiple APIC Description Table (MADT) - struct madt : vla_table - { - //! Get the physical address of the local interrupt controllers described by this entry. - [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t; - - [[nodiscard]] auto flags() const noexcept -> std::uint32_t; - - template - [[nodiscard]] auto only() const noexcept - { - return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) | - std::views::transform([](auto const & e) { return e.template as(); }); - } - - private: - std::array m_data{}; - }; - - struct processor_local_apic_entry : madt_entry - { - constexpr auto static this_type = type::processor_local_apic; - - enum struct flags : std::uint32_t - { - processor_enabled = 1, - online_capable = 2, - }; - - [[nodiscard]] auto id() const noexcept -> std::uint8_t; - [[nodiscard]] auto flags() const noexcept -> flags; - [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t; - - private: - std::array m_data{}; - }; - -} // namespace acpi - -template<> -struct kstd::ext::is_bitfield_enum : std::true_type -{ -}; - -#endif diff --git a/libs/acpi/acpi/madt.test.cpp b/libs/acpi/acpi/madt.test.cpp deleted file mode 100644 index 899a377..0000000 --- a/libs/acpi/acpi/madt.test.cpp +++ /dev/null @@ -1,55 +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() == 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); - } - } - } -} -- cgit v1.2.3 From b31c47b32d91b0b85245ed30f1751cd5cbc397cf Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 09:37:41 +0200 Subject: acpi: add rsdt type --- libs/acpi/CMakeLists.txt | 2 ++ libs/acpi/acpi/data/rsdt.cpp | 37 +++++++++++++++++++++++++++++++ libs/acpi/acpi/data/rsdt.hpp | 42 +++++++++++++++++++++++++++++++++++ libs/acpi/acpi/data/rsdt.test.cpp | 46 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 libs/acpi/acpi/data/rsdt.cpp create mode 100644 libs/acpi/acpi/data/rsdt.hpp create mode 100644 libs/acpi/acpi/data/rsdt.test.cpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index f6e484c..833dcea 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources("acpi" PRIVATE "acpi/common/checksum.cpp" "acpi/common/table_header.cpp" "acpi/data/madt.cpp" + "acpi/data/rsdt.cpp" "acpi/pointers.cpp" ) @@ -61,6 +62,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_executable("acpi_tests" "acpi/common/table_header.test.cpp" "acpi/data/madt.test.cpp" + "acpi/data/rsdt.test.cpp" "acpi/pointers.test.cpp" "test_data/tables.S" diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp new file mode 100644 index 0000000..8e20b6c --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include + +#include +#include + +namespace acpi +{ + + struct rsdt_entry_data + { + std::uint32_t address; + }; + + [[nodiscard]] auto rsdt_entry::address() const noexcept -> table_header * + { + using type = decltype(rsdt_entry_data::address); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(rsdt_entry_data, address); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return reinterpret_cast(static_cast(value)); + } + + [[nodiscard]] auto rsdt_entry::length() const noexcept -> std::size_t + { + return sizeof(m_data); + } + +} // namespace acpi \ No newline at end of file diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp new file mode 100644 index 0000000..b43f066 --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.hpp @@ -0,0 +1,42 @@ +#ifndef ACPI_DATA_RSDT_HPP +#define ACPI_DATA_RSDT_HPP + +#include +#include +#include +#include + +#include +#include + +namespace acpi +{ + + template<> + struct table_signature + { + constexpr char static const value[] = "RSDT"; // NOLINT + }; + + template<> + struct table_type> + { + using type = struct rsdt; + }; + + struct rsdt_entry + { + [[nodiscard]] auto address() const noexcept -> table_header *; + [[nodiscard]] auto length() const noexcept -> std::size_t; + + private: + std::array m_data{}; + }; + + struct rsdt : vla_table + { + }; + +} // namespace acpi + +#endif \ No newline at end of file diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp new file mode 100644 index 0000000..e1bd1df --- /dev/null +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -0,0 +1,46 @@ +#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() == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry)); + } + } + } +} -- cgit v1.2.3 From 28cae58fe117e5fcfc46fd6378e19387cd73b2fe Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 09:40:12 +0200 Subject: acpi: derive basic table from table header --- libs/acpi/acpi/acpi.hpp | 1 + libs/acpi/acpi/common/basic_table.hpp | 25 +++---------------------- libs/acpi/acpi/common/vla_table.hpp | 4 ++-- libs/acpi/acpi/data/madt.test.cpp | 2 +- libs/acpi/acpi/data/rsdt.cpp | 1 + libs/acpi/acpi/data/rsdt.hpp | 1 - libs/acpi/acpi/data/rsdt.test.cpp | 2 +- 7 files changed, 9 insertions(+), 27 deletions(-) (limited to 'libs') diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp index 4cfcede..385ddc9 100644 --- a/libs/acpi/acpi/acpi.hpp +++ b/libs/acpi/acpi/acpi.hpp @@ -5,6 +5,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #endif diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp index b0ead96..33f23d5 100644 --- a/libs/acpi/acpi/common/basic_table.hpp +++ b/libs/acpi/acpi/common/basic_table.hpp @@ -7,46 +7,27 @@ #include #include -#include namespace acpi { template - struct basic_table + struct basic_table : table_header { [[nodiscard]] auto validate_checksum() const noexcept -> bool { - return acpi::validate_checksum({reinterpret_cast(this), m_header.length().value}); + return acpi::validate_checksum(as_span()); } [[nodiscard]] auto as_span() const noexcept -> std::span { - return {reinterpret_cast(this), m_header.length().value}; - } - - [[nodiscard]] auto header() const noexcept -> table_header const & - { - return m_header; - } - - [[nodiscard]] auto length() const noexcept -> std::size_t - { - return m_header.length().value; - } - - [[nodiscard]] auto signature() const noexcept -> std::string_view - { - return m_header.signature(); + return {reinterpret_cast(this), length().value}; } [[nodiscard]] auto validate() const noexcept -> bool { return signature() == table_signature_v && validate_checksum(); } - - private: - table_header m_header; }; } // namespace acpi diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp index 450209c..a65a28e 100644 --- a/libs/acpi/acpi/common/vla_table.hpp +++ b/libs/acpi/acpi/common/vla_table.hpp @@ -77,7 +77,7 @@ namespace acpi auto base = std::bit_cast(this); base += sizeof(TableType); auto limit = std::bit_cast(this); - limit += this->length(); + limit += this->length().value; return iterator{std::bit_cast(base), std::bit_cast(limit)}; } @@ -91,7 +91,7 @@ namespace acpi auto base = std::bit_cast(this); base += sizeof(TableType); auto limit = std::bit_cast(this); - limit += this->length(); + limit += this->length().value; return const_iterator{std::bit_cast(base), std::bit_cast(limit)}; } diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp index 2b74d4c..6795499 100644 --- a/libs/acpi/acpi/data/madt.test.cpp +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -38,7 +38,7 @@ SCENARIO("MADT parsing", "[madt]") THEN("the length is sizeof(madt) + sizeof(processor_local_apic)") { - REQUIRE(madt->length() == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry)); + REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry)); } THEN("the first entry has type processor_local_apic") diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp index 8e20b6c..fe108c7 100644 --- a/libs/acpi/acpi/data/rsdt.cpp +++ b/libs/acpi/acpi/data/rsdt.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace acpi { diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp index b43f066..88c5fd1 100644 --- a/libs/acpi/acpi/data/rsdt.hpp +++ b/libs/acpi/acpi/data/rsdt.hpp @@ -1,7 +1,6 @@ #ifndef ACPI_DATA_RSDT_HPP #define ACPI_DATA_RSDT_HPP -#include #include #include #include diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp index e1bd1df..937dce0 100644 --- a/libs/acpi/acpi/data/rsdt.test.cpp +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -39,7 +39,7 @@ SCENARIO("RSDT parsing", "[rsdt]") THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)") { - REQUIRE(rsdt->length() == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry)); + REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry)); } } } -- cgit v1.2.3 From 776ab2749d5af0a34fd2aa6103a377ddc04d4c53 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 10:03:35 +0200 Subject: acpi: introduce XSDT type --- libs/acpi/CMakeLists.txt | 3 +++ libs/acpi/acpi/acpi.hpp | 1 + libs/acpi/acpi/data/rsdt.hpp | 1 + libs/acpi/acpi/data/xsdt.cpp | 38 +++++++++++++++++++++++++++++++ libs/acpi/acpi/data/xsdt.hpp | 42 ++++++++++++++++++++++++++++++++++ libs/acpi/acpi/data/xsdt.test.cpp | 46 ++++++++++++++++++++++++++++++++++++++ libs/acpi/test_data/basic_xsdt.asl | 26 +++++++++++++++++++++ libs/acpi/test_data/tables.S | 1 + libs/acpi/test_data/tables.hpp | 1 + 9 files changed, 159 insertions(+) create mode 100644 libs/acpi/acpi/data/xsdt.cpp create mode 100644 libs/acpi/acpi/data/xsdt.hpp create mode 100644 libs/acpi/acpi/data/xsdt.test.cpp create mode 100644 libs/acpi/test_data/basic_xsdt.asl (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 833dcea..55d5b54 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources("acpi" PRIVATE "acpi/common/table_header.cpp" "acpi/data/madt.cpp" "acpi/data/rsdt.cpp" + "acpi/data/xsdt.cpp" "acpi/pointers.cpp" ) @@ -44,6 +45,7 @@ if(NOT CMAKE_CROSSCOMPILING) "basic_madt" "basic_rsdt" "basic_rsdp" + "basic_xsdt" "table_header" ) @@ -63,6 +65,7 @@ if(NOT CMAKE_CROSSCOMPILING) "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" "test_data/tables.S" diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp index 385ddc9..0da44a8 100644 --- a/libs/acpi/acpi/acpi.hpp +++ b/libs/acpi/acpi/acpi.hpp @@ -6,6 +6,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #endif diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp index 88c5fd1..a661187 100644 --- a/libs/acpi/acpi/data/rsdt.hpp +++ b/libs/acpi/acpi/data/rsdt.hpp @@ -1,6 +1,7 @@ #ifndef ACPI_DATA_RSDT_HPP #define ACPI_DATA_RSDT_HPP +#include #include #include #include diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp new file mode 100644 index 0000000..b4202b8 --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include + +#include +#include +#include + +namespace acpi +{ + + struct xsdt_entry_data + { + std::uint64_t address; + }; + + [[nodiscard]] auto xsdt_entry::address() const noexcept -> table_header * + { + using type = decltype(xsdt_entry_data::address); + + constexpr auto size = sizeof(type); + constexpr auto offset = offsetof(xsdt_entry_data, address); + + auto const data = std::span{m_data.data() + offset, size}; + auto value = type{}; + + kstd::libc::memcpy(&value, data.data(), size); + + return reinterpret_cast(static_cast(value)); + } + + [[nodiscard]] auto xsdt_entry::length() const noexcept -> std::size_t + { + return sizeof(m_data); + } + +} // namespace acpi \ No newline at end of file diff --git a/libs/acpi/acpi/data/xsdt.hpp b/libs/acpi/acpi/data/xsdt.hpp new file mode 100644 index 0000000..965f23c --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.hpp @@ -0,0 +1,42 @@ +#ifndef ACPI_DATA_XSDT_HPP +#define ACPI_DATA_XSDT_HPP + +#include +#include +#include +#include + +#include +#include + +namespace acpi +{ + + template<> + struct table_signature + { + constexpr char static const value[] = "XSDT"; // NOLINT + }; + + template<> + struct table_type> + { + using type = struct xsdt; + }; + + struct xsdt_entry + { + [[nodiscard]] auto address() const noexcept -> table_header *; + [[nodiscard]] auto length() const noexcept -> std::size_t; + + private: + std::array m_data{}; + }; + + struct xsdt : vla_table + { + }; + +} // namespace acpi + +#endif \ No newline at end of file diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp new file mode 100644 index 0000000..7fb564c --- /dev/null +++ b/libs/acpi/acpi/data/xsdt.test.cpp @@ -0,0 +1,46 @@ +#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/test_data/basic_xsdt.asl b/libs/acpi/test_data/basic_xsdt.asl new file mode 100644 index 0000000..d8589f9 --- /dev/null +++ b/libs/acpi/test_data/basic_xsdt.asl @@ -0,0 +1,26 @@ +/* + * Intel ACPI Component Architecture + * iASL Compiler/Disassembler version 20251212 (64-bit version) + * Copyright (c) 2000 - 2025 Intel Corporation + * + * Template for [XSDT] ACPI Table (static data table) + * Format: [ByteLength] FieldName : HexFieldValue + */ +[0004] Signature : "XSDT" [Extended System Description Table] +[0004] Table Length : 00000064 +[0001] Revision : 01 +[0001] Checksum : 8B +[0006] Oem ID : "INTEL " +[0008] Oem Table ID : "TEMPLATE" +[0004] Oem Revision : 00000001 +[0004] Asl Compiler ID : "INTL" +[0004] Asl Compiler Revision : 20100528 + +[0008] ACPI Table Address 0 : 0000000000000010 +[0008] ACPI Table Address 1 : 0000000000000020 +[0008] ACPI Table Address 2 : 0000000000000030 +[0008] ACPI Table Address 3 : 0000000000000040 +[0008] ACPI Table Address 4 : 0000000000000050 +[0008] ACPI Table Address 5 : 0000000000000060 +[0008] ACPI Table Address 6 : 0000000000000070 +[0008] ACPI Table Address 7 : 0000000000000080 diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S index da9a12f..641db6a 100644 --- a/libs/acpi/test_data/tables.S +++ b/libs/acpi/test_data/tables.S @@ -11,6 +11,7 @@ TABLE(basic_madt, "basic_madt.aml") TABLE(basic_rsdt, "basic_rsdt.aml") TABLE(basic_rsdp, "basic_rsdp.aml") +TABLE(basic_xsdt, "basic_xsdt.aml") TABLE(table_header, "table_header.aml") #undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp index dc39b37..e91f1a5 100644 --- a/libs/acpi/test_data/tables.hpp +++ b/libs/acpi/test_data/tables.hpp @@ -18,6 +18,7 @@ namespace acpi::test_data::tables TABLE(basic_madt); TABLE(basic_rsdt); TABLE(basic_rsdp); + TABLE(basic_xsdt); TABLE(table_header); } // namespace acpi::test_data::tables -- cgit v1.2.3 From 9b4cbc6ba3f8059278a20a4893780717851ce8e4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 21 Apr 2026 13:06:35 +0200 Subject: build: clean up configuration --- libs/acpi/CMakeLists.txt | 64 ++++++++++++++++++++++------ libs/elf/CMakeLists.txt | 43 ++++++++++++++++++- libs/kstd/CMakeLists.txt | 97 +++++++++++++++++++++++++++++++----------- libs/multiboot2/CMakeLists.txt | 49 ++++++++++++++++++--- 4 files changed, 209 insertions(+), 44 deletions(-) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 55d5b54..b0fc48f 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -6,17 +6,35 @@ project("acpi" LANGUAGES ASM CXX ) -add_library("acpi" STATIC) -add_library("libs::acpi" ALIAS "acpi") +include("CTest") -target_include_directories("acpi" PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}" -) +#[============================================================================[ +# External Dependencies +#]============================================================================] -file(GLOB_RECURSE ACPI_HEADERS - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - "acpi/*.hpp" -) +include("FetchContent") + +if (BUILD_TESTING) + FetchContent_Declare( + "Catch2" + URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" + URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + + FetchContent_MakeAvailable("Catch2") + + find_package("Catch2") + include("Catch") +endif() + +#[============================================================================[ +# Library +#]============================================================================] + +add_library("acpi" STATIC) +add_library("acpi::lib" ALIAS "acpi") target_sources("acpi" PRIVATE "acpi/common/checksum.cpp" @@ -27,6 +45,11 @@ target_sources("acpi" PRIVATE "acpi/pointers.cpp" ) +file(GLOB_RECURSE ACPI_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "acpi/*.hpp" +) + target_sources("acpi" PUBLIC FILE_SET HEADERS BASE_DIRS "acpi" @@ -34,11 +57,23 @@ target_sources("acpi" PUBLIC ${ACPI_HEADERS} ) +target_include_directories("acpi" PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + target_link_libraries("acpi" PUBLIC - "libs::kstd" + "kstd::lib" ) -if(NOT CMAKE_CROSSCOMPILING) +set_target_properties("acpi" PROPERTIES + VERIFY_INTERFACE_HEADER_SETS YES +) + +#[============================================================================[ +# Tests +#]============================================================================] + +if(BUILD_TESTING) find_program(IASL_EXE NAMES "iasl" REQUIRED) set(TEST_TABLES @@ -61,7 +96,10 @@ if(NOT CMAKE_CROSSCOMPILING) set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") - add_executable("acpi_tests" + add_executable("acpi_tests") + 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" @@ -77,7 +115,7 @@ if(NOT CMAKE_CROSSCOMPILING) target_link_libraries("acpi_tests" PRIVATE "Catch2::Catch2WithMain" - "libs::acpi" + "acpi::lib" ) set_target_properties("acpi_tests" PROPERTIES diff --git a/libs/elf/CMakeLists.txt b/libs/elf/CMakeLists.txt index f254094..f1f5275 100644 --- a/libs/elf/CMakeLists.txt +++ b/libs/elf/CMakeLists.txt @@ -1,7 +1,46 @@ +cmake_minimum_required(VERSION "3.27.0") + +project("elf" + DESCRIPTION "ELF file format library" + VERSION "0.0.1" + LANGUAGES CXX +) + +include("CTest") + +#[============================================================================[ +# External Dependencies +#]============================================================================] + +include("FetchContent") + +if (BUILD_TESTING) + FetchContent_Declare( + "Catch2" + URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" + URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + + FetchContent_MakeAvailable("Catch2") + + find_package("Catch2") + include("Catch") +endif() + +#[============================================================================[ +# Library +#]============================================================================] + add_library("elf" INTERFACE) -add_library("libs::elf" ALIAS "elf") +add_library("elf::lib" ALIAS "elf") -file(GLOB_RECURSE ELF_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") +file(GLOB_RECURSE ELF_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + CONFIGURE_DEPENDS + "include/**.hpp" +) target_sources("elf" INTERFACE FILE_SET HEADERS diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index ced3138..ee84ff1 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -1,30 +1,52 @@ -add_library("kstd" STATIC) -add_library("libs::kstd" ALIAS "kstd") +cmake_minimum_required(VERSION "3.27.0") -if(CMAKE_CROSSCOMPILING) - set(KSTD_LIBC_SYMBOLS - "abort" - "strlen" - "memcmp" - "memcpy" - ) +project("kstd" + DESCRIPTION "A kernel STL implementation" + VERSION "0.0.1" + LANGUAGES CXX +) - set(KSTD_LIBC_SOURCES - "src/libc/stdlib.cpp" - "src/libc/string.cpp" - ) +include("CTest") + +#[============================================================================[ +# External Dependencies +#]============================================================================] + +include("FetchContent") + +if (BUILD_TESTING) + FetchContent_Declare( + "Catch2" + URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" + URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + + FetchContent_MakeAvailable("Catch2") + + find_package("Catch2") + include("Catch") endif() -target_sources("kstd" PRIVATE - ${KSTD_LIBC_SOURCES} +#[============================================================================[ +# Library +#]============================================================================] - "src/os/error.cpp" +add_library("kstd" STATIC) +add_library("kstd::lib" ALIAS "kstd") +target_sources("kstd" PRIVATE + "src/os/error.cpp" "src/mutex.cpp" "src/vformat.cpp" ) -file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*") +file(GLOB_RECURSE KSTD_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + CONFIGURE_DEPENDS + "include/kstd/*" +) target_sources("kstd" PUBLIC FILE_SET HEADERS @@ -37,12 +59,37 @@ target_include_directories("kstd" PUBLIC "include" ) -if(CMAKE_CROSSCOMPILING) +set_target_properties("kstd" PROPERTIES + VERIFY_INTERFACE_HEADER_SETS YES +) + +if(NOT BUILD_TESTING) + target_sources("kstd" PRIVATE + "src/libc/stdlib.cpp" + "src/libc/string.cpp" + ) + + set(KSTD_LIBC_SYMBOLS + "abort" + "strlen" + "memcmp" + "memcpy" + ) + list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=") target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) -else() - add_executable("kstd_tests" +endif() + +#[============================================================================[ +# Tests +#]============================================================================] + +if(BUILD_TESTING) + add_executable("kstd_tests") + add_executable("kstd::tests" ALIAS "kstd_tests") + + target_sources("kstd_tests" PRIVATE "tests/src/flat_map.cpp" "tests/src/format.cpp" "tests/src/vector.cpp" @@ -57,7 +104,7 @@ else() target_link_libraries("kstd_tests" PRIVATE "Catch2::Catch2WithMain" - "libs::kstd" + "kstd::lib" ) set_target_properties("kstd_tests" PROPERTIES @@ -66,8 +113,10 @@ else() EXCLUDE_FROM_ALL NO ) - enable_coverage("kstd") - enable_coverage("kstd_tests") + if(COMMAND "enable_coverage") + enable_coverage("kstd") + enable_coverage("kstd_tests") + endif() - catch_discover_tests("kstd_tests") + catch_discover_tests("kstd::tests") endif() \ No newline at end of file diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt index 7384a3d..b56b0ba 100644 --- a/libs/multiboot2/CMakeLists.txt +++ b/libs/multiboot2/CMakeLists.txt @@ -1,7 +1,46 @@ +cmake_minimum_required(VERSION "3.27.0") + +project("multiboot2" + DESCRIPTION "Multiboot2 bootloader specification library" + VERSION "0.0.1" + LANGUAGES CXX +) + +include("CTest") + +#[============================================================================[ +# External Dependencies +#]============================================================================] + +include("FetchContent") + +if (BUILD_TESTING) + FetchContent_Declare( + "Catch2" + URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" + URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + + FetchContent_MakeAvailable("Catch2") + + find_package("Catch2") + include("Catch") +endif() + +#[============================================================================[ +# Library +#]============================================================================] + add_library("multiboot2" INTERFACE) -add_library("libs::multiboot2" ALIAS "multiboot2") +add_library("multiboot2::lib" ALIAS "multiboot2") -file(GLOB_RECURSE MULTIBOOT2_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp") +file(GLOB_RECURSE MULTIBOOT2_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + CONFIGURE_DEPENDS + "include/**.hpp" +) target_sources("multiboot2" INTERFACE FILE_SET HEADERS @@ -15,10 +54,10 @@ target_include_directories("multiboot2" INTERFACE ) target_link_libraries("multiboot2" INTERFACE - "libs::elf" - "libs::kstd" + "elf::lib" + "kstd::lib" ) set_target_properties("multiboot2" PROPERTIES VERIFY_INTERFACE_HEADER_SETS YES -) \ No newline at end of file +) -- cgit v1.2.3 From e3fa6b1adbd7fce3b080d75fd0959949b7d3bef4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 21 Apr 2026 14:13:55 +0200 Subject: acpi: enable test coverage --- libs/acpi/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index b0fc48f..b4d11d9 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -46,6 +46,7 @@ target_sources("acpi" PRIVATE ) file(GLOB_RECURSE ACPI_HEADERS + CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "acpi/*.hpp" ) @@ -96,6 +97,10 @@ if(BUILD_TESTING) set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") + if(COMMAND "enable_coverage") + enable_coverage("acpi") + endif() + add_executable("acpi_tests") add_executable("acpi::tests" ALIAS "acpi_tests") -- cgit v1.2.3 From e8ce02d63f096147fc54824f0a45c23e3a3ced25 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 21 Apr 2026 14:44:01 +0200 Subject: libs: prepare for clang-tidy --- libs/acpi/CMakeLists.txt | 2 -- libs/kstd/CMakeLists.txt | 2 -- libs/kstd/include/kstd/bits/format/vformat.hpp | 2 +- libs/kstd/tests/src/format.cpp | 2 -- libs/kstd/tests/src/observer_ptr.cpp | 3 ++- libs/kstd/tests/src/string.cpp | 8 ++++---- libs/kstd/tests/src/vector.cpp | 18 +++++++++--------- 7 files changed, 16 insertions(+), 21 deletions(-) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index b4d11d9..e73c6b3 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -124,8 +124,6 @@ if(BUILD_TESTING) ) set_target_properties("acpi_tests" PROPERTIES - C_CLANG_TIDY "" - CXX_CLANG_TIDY "" EXCLUDE_FROM_ALL NO ) diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index ee84ff1..2b5ee12 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -108,8 +108,6 @@ if(BUILD_TESTING) ) set_target_properties("kstd_tests" PROPERTIES - C_CLANG_TIDY "" - CXX_CLANG_TIDY "" EXCLUDE_FROM_ALL NO ) diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index 4fec7dd..994fae5 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -45,7 +45,7 @@ namespace kstd : m_output{iterator} {} - auto iterator() const -> Output + [[nodiscard]] auto iterator() const -> Output { return m_output; } diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp index 73c8102..624779a 100644 --- a/libs/kstd/tests/src/format.cpp +++ b/libs/kstd/tests/src/format.cpp @@ -1,6 +1,4 @@ -#include #include -#include #include diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp index 006ebde..0c94892 100644 --- a/libs/kstd/tests/src/observer_ptr.cpp +++ b/libs/kstd/tests/src/observer_ptr.cpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -313,7 +314,7 @@ SCENARIO("Observer pointer comparisons", "[observer_ptr]") { GIVEN("Observer pointers to elements of an array") { - int arr[] = {1, 2}; + auto arr = std::array{1, 2}; auto ptr1 = kstd::observer_ptr{&arr[0]}; auto ptr2 = kstd::observer_ptr{&arr[1]}; diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index 43e9a6b..53d7c9a 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include SCENARIO("String initialization and construction", "[string]") @@ -59,7 +59,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has the same size as the C-style string") { REQUIRE(!str.empty()); - REQUIRE(str.size() == strlen(c_str)); + REQUIRE(str.size() == std::strlen(c_str)); } THEN("the string contains the same characters as the C-style string") @@ -266,8 +266,8 @@ SCENARIO("String conversion and comparison", "[string]") { GIVEN("An unsigned integer") { - auto value1 = 12345u; - auto value2 = 0u; + constexpr auto value1 = 12345u; + constexpr auto value2 = 0u; WHEN("converting the unsigned integer to a string") { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 5faaaff..b826820 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -492,27 +492,27 @@ SCENARIO("Vector modifiers", "[vector]") WHEN("inserting an element") { - auto it = v.insert(v.cbegin(), 42); + 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] == 42); + REQUIRE(v[0] == 40); REQUIRE(it == v.begin()); } } WHEN("inserting an lvalue element") { - auto const value = 42; + 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] == 42); + REQUIRE(v[0] == 40); REQUIRE(it == v.begin()); } } @@ -927,7 +927,7 @@ SCENARIO("Vector with non-default-constructible types", "[vector]") WHEN("using emplace_back") { auto v = kstd::vector{}; - v.emplace_back(42); + v.emplace_back(40); THEN("the element is added and the size and capacity increase") { @@ -965,7 +965,7 @@ 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{42}; + auto tracker = kstd::tests::special_member_tracker{40}; WHEN("push_back is called with the move tracker") { @@ -976,7 +976,7 @@ SCENARIO("Vector modifier move semantics", "[vector]") REQUIRE(v.size() == 1); REQUIRE(v.back().move_constructed_count == 1); REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 42); + REQUIRE(v.back().value == 40); } THEN("the original tracker is left in a valid but unspecified state") @@ -992,14 +992,14 @@ SCENARIO("Vector modifier move semantics", "[vector]") WHEN("emplace_back is called with constructor arguments") { - v.emplace_back(42); + 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 == 42); + REQUIRE(v.back().value == 40); } } } -- cgit v1.2.3 From 743b41956d5dab989a7e408fd7e69ac0bc5afb8c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 22 Apr 2026 06:56:07 +0200 Subject: kstd: fix vector tests --- libs/kstd/tests/src/vector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index b826820..97460b4 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -932,7 +932,7 @@ SCENARIO("Vector with non-default-constructible types", "[vector]") THEN("the element is added and the size and capacity increase") { REQUIRE(v.size() == 1); - REQUIRE(v.back() == kstd::tests::non_default_constructible{42}); + REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); } } } -- cgit v1.2.3 From 4a3d755ca8d64fe81930b93b1f90411f290976fd Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 22 Apr 2026 09:31:03 +0200 Subject: multiboot2: add test MBI dump --- libs/multiboot2/test_data/mbi.bin | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 libs/multiboot2/test_data/mbi.bin (limited to 'libs') diff --git a/libs/multiboot2/test_data/mbi.bin b/libs/multiboot2/test_data/mbi.bin new file mode 100644 index 0000000..4985526 --- /dev/null +++ b/libs/multiboot2/test_data/mbi.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b67ca95183fde8e7dc8dac2d20af9331122128127926ebe6f8bd80ca9fed7c3 +size 1176 -- cgit v1.2.3 From dec3c3b0387ec477125db21e741bc492d3475db5 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 22 Apr 2026 09:32:22 +0200 Subject: multiboot2: rearrange project structure --- libs/multiboot2/CMakeLists.txt | 2 +- libs/multiboot2/include/multiboot2/constants.hpp | 15 - .../multiboot2/constants/architecture_id.hpp | 22 -- .../multiboot2/constants/information_id.hpp | 86 ------ .../include/multiboot2/constants/memory_type.hpp | 33 --- .../include/multiboot2/constants/tag_id.hpp | 29 -- libs/multiboot2/include/multiboot2/information.hpp | 308 --------------------- .../include/multiboot2/information/data.hpp | 184 ------------ .../include/multiboot2/information/iterator.hpp | 73 ----- .../include/multiboot2/information/tag.hpp | 206 -------------- libs/multiboot2/multiboot2/constants.hpp | 15 + .../multiboot2/constants/architecture_id.hpp | 22 ++ .../multiboot2/constants/information_id.hpp | 86 ++++++ .../multiboot2/constants/memory_type.hpp | 33 +++ libs/multiboot2/multiboot2/constants/tag_id.hpp | 29 ++ libs/multiboot2/multiboot2/information.hpp | 308 +++++++++++++++++++++ libs/multiboot2/multiboot2/information/data.hpp | 184 ++++++++++++ .../multiboot2/multiboot2/information/iterator.hpp | 73 +++++ libs/multiboot2/multiboot2/information/tag.hpp | 206 ++++++++++++++ 19 files changed, 957 insertions(+), 957 deletions(-) delete mode 100644 libs/multiboot2/include/multiboot2/constants.hpp delete mode 100644 libs/multiboot2/include/multiboot2/constants/architecture_id.hpp delete mode 100644 libs/multiboot2/include/multiboot2/constants/information_id.hpp delete mode 100644 libs/multiboot2/include/multiboot2/constants/memory_type.hpp delete mode 100644 libs/multiboot2/include/multiboot2/constants/tag_id.hpp delete mode 100644 libs/multiboot2/include/multiboot2/information.hpp delete mode 100644 libs/multiboot2/include/multiboot2/information/data.hpp delete mode 100644 libs/multiboot2/include/multiboot2/information/iterator.hpp delete mode 100644 libs/multiboot2/include/multiboot2/information/tag.hpp create mode 100644 libs/multiboot2/multiboot2/constants.hpp create mode 100644 libs/multiboot2/multiboot2/constants/architecture_id.hpp create mode 100644 libs/multiboot2/multiboot2/constants/information_id.hpp create mode 100644 libs/multiboot2/multiboot2/constants/memory_type.hpp create mode 100644 libs/multiboot2/multiboot2/constants/tag_id.hpp create mode 100644 libs/multiboot2/multiboot2/information.hpp create mode 100644 libs/multiboot2/multiboot2/information/data.hpp create mode 100644 libs/multiboot2/multiboot2/information/iterator.hpp create mode 100644 libs/multiboot2/multiboot2/information/tag.hpp (limited to 'libs') diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt index b56b0ba..da5fb53 100644 --- a/libs/multiboot2/CMakeLists.txt +++ b/libs/multiboot2/CMakeLists.txt @@ -50,7 +50,7 @@ target_sources("multiboot2" INTERFACE ) target_include_directories("multiboot2" INTERFACE - "include" + "${CMAKE_CURRENT_SOURCE_DIR}" ) target_link_libraries("multiboot2" INTERFACE diff --git a/libs/multiboot2/include/multiboot2/constants.hpp b/libs/multiboot2/include/multiboot2/constants.hpp deleted file mode 100644 index 2198210..0000000 --- a/libs/multiboot2/include/multiboot2/constants.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef MULTIBOOT2_CONSTANTS_HPP -#define MULTIBOOT2_CONSTANTS_HPP - -#include "constants/architecture_id.hpp" // IWYU pragma: export - -#include - -namespace multiboot2 -{ - - constexpr auto inline header_magic = std::uint32_t{0xe852'50d6}; - -} // namespace multiboot2 - -#endif diff --git a/libs/multiboot2/include/multiboot2/constants/architecture_id.hpp b/libs/multiboot2/include/multiboot2/constants/architecture_id.hpp deleted file mode 100644 index e13c471..0000000 --- a/libs/multiboot2/include/multiboot2/constants/architecture_id.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MULTIBOOT2_CONSTANTS_ARCHITECTURE_ID_HPP -#define MULTIBOOT2_CONSTANTS_ARCHITECTURE_ID_HPP - -// IWYU pragma: private, include - -#include - -namespace multiboot2 -{ - - //! The IDs of the supported system architectures. - enum struct architecture_id : std::uint32_t - { - //! 32-bit protected mode i386 - i386 = 0, - //! 32-bit MIPS - mips32 = 4, - }; - -} // namespace multiboot2 - -#endif \ No newline at end of file diff --git a/libs/multiboot2/include/multiboot2/constants/information_id.hpp b/libs/multiboot2/include/multiboot2/constants/information_id.hpp deleted file mode 100644 index 27c5300..0000000 --- a/libs/multiboot2/include/multiboot2/constants/information_id.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef MULTIBOOT2_CONSTANTS_INFORMATION_ID_HPP -#define MULTIBOOT2_CONSTANTS_INFORMATION_ID_HPP - -// IWYU pragma: private, include - -#include - -namespace multiboot2 -{ - - //! The IDs of all Multiboot2 information tags. - //! - //! Each tag provided by the boot loader will be tagged with one of these IDs. A tags data format can thus be deduced - //! from it's ID. - enum struct information_id : std::uint32_t - { - //! Signals final tag for the multiboot2 information structure. - end, - - //! The command line string. - command_line, - - //! The name of the boot loader booting the kernel. - boot_loader_name, - - //! Indicates the boot module which was loaded along the kernel image. - module, - - //! The amount of lower and upper (above 1 MiB) memory. - basic_memory_information, - - //! Indicates which BIOS disk device the hoot loader has loaded the OS image from. - boot_device, - - //! Describes the memory layout of the system with individual areas and their flags. - memory_map, - - //! Includes information to access and utilize the device GPU. - vbe_information, - - //! VBE framebuffer information. - framebuferr_information, - - //! Includes list of all section headers from the loaded ELF kernel. - elf_sections, - - //! Advanced Power Management information. - apm_information, - - //! The pointer to the EFI 32 bit system table. - efi32_system_table_pointer, - - //! The pointer to the EFI 64 bit system table. - efi64_system_table_pointer, - - //! A copy of all System Management BIOS tables. - smbios_tables, - - //! A copy of RSDP as defined per ACPI 1.0 specification. - acpi_rsdp, - - //! A copy of XSDP as defined per ACPI 2.0 or later specification. - acpi_xsdp, - - //! The network information specified specified as per DHCP. - networking_information, - - //! The memory map as provided by the EFI. - efi_memory_map, - - //! Indicates ExitBootServices wasn't called. - boot_services_not_terminated, - - //! EFI 32 bit image handle pointer. - efi32_image_handle_pointer, - - //! EFI 64 bit image handle pointer. - efi64_image_handle_pointer, - - //! The physical image load base address. - image_load_base_address - }; - -} // namespace multiboot2 - -#endif diff --git a/libs/multiboot2/include/multiboot2/constants/memory_type.hpp b/libs/multiboot2/include/multiboot2/constants/memory_type.hpp deleted file mode 100644 index 6be94bd..0000000 --- a/libs/multiboot2/include/multiboot2/constants/memory_type.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MULTIBOOT2_CONSTANTS_MEMORY_TYPE_HPP -#define MULTIBOOT2_CONSTANTS_MEMORY_TYPE_HPP - -// IWYU pragma: private, include - -#include - -namespace multiboot2 -{ - - //! The types of memory potentially present in the detailed memory map. - enum struct memory_type : std::uint32_t - { - //! The memory is available for general use by the operating system. - available = 1, - - //! The memory is reserved for firmware or other purposes and must not be used for general purposes by the operating - //! system. - reserved, - - //! The memory contains ACPI data and can be reclaimed when ACPI is not in use. - acpi_reclaimable, - - //! The memory is reserved for non-volatile storage. - non_volatile_storage, - - //! The memory range is occupied by defective RAM. - bad_ram, - }; - -} // namespace multiboot2 - -#endif \ No newline at end of file diff --git a/libs/multiboot2/include/multiboot2/constants/tag_id.hpp b/libs/multiboot2/include/multiboot2/constants/tag_id.hpp deleted file mode 100644 index 23d39cc..0000000 --- a/libs/multiboot2/include/multiboot2/constants/tag_id.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef MULTIBOOT2_CONSTANTS_TAG_ID_HPP -#define MULTIBOOT2_CONSTANTS_TAG_ID_HPP - -// IWYU pragma: private, include - -#include - -namespace multiboot2 -{ - - //! The IDs for tags to be used in the image header. - enum struct tag_id : std::uint32_t - { - end, - information_request, - addresses, - entry_address, - console_flags, - preferred_framebuffer_mode, - page_align_modules, - efi_boot_services_supported, - efi32_entry_address, - efi64_entry_address, - relocatable_image, - }; - -} // namespace multiboot2 - -#endif \ No newline at end of file diff --git a/libs/multiboot2/include/multiboot2/information.hpp b/libs/multiboot2/include/multiboot2/information.hpp deleted file mode 100644 index a2ded56..0000000 --- a/libs/multiboot2/include/multiboot2/information.hpp +++ /dev/null @@ -1,308 +0,0 @@ -#ifndef MULTIBOOT2_INFORMATION_HPP -#define MULTIBOOT2_INFORMATION_HPP - -#include "information/data.hpp" // IWYU pragma: export -#include "information/iterator.hpp" // IWYU pragma: export -#include "information/tag.hpp" // IWYU pragma: export - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace multiboot2 -{ - - /** - * @copydoc multiboot2::data::basic_memory - */ - struct basic_memory : tag - { - using tag::tag; - }; - - /** - * @copydoc multiboot2::data::bios_boot_device - */ - struct bios_boot_device : tag - { - using tag::tag; - }; - - /** - * @copydoc multiboot2::data::command_line - */ - struct command_line : vla_tag - { - using vla_tag::vla_tag; - - /** - * @brief The command line string - */ - [[nodiscard]] auto string() const noexcept -> range_type - { - return {data(), size()}; - } - }; - - /** - * @copydoc multiboot2::data::elf_symbols - */ - template - struct elf_symbols : vla_tag const, std::span> - { - using base = vla_tag const, std::span>; - using base::base; - - [[nodiscard]] auto name(elf::section_header const & section) const noexcept -> std::string_view - { - if (!this->string_table_index) - { - std::abort(); - } - - auto string_table = this->begin()[this->string_table_index]; - auto name_offset = section.name_offset; - auto name_array = std::bit_cast(string_table.virtual_load_address); - return name_array + name_offset; - } - }; - - /** - * @copydoc multiboot2::data::loader_name - */ - struct loader_name : vla_tag - { - using vla_tag::vla_tag; - - /** - * @brief The name of the bootloader - */ - [[nodiscard]] auto string() const noexcept -> std::string_view - { - return {data(), size()}; - } - }; - - /** - * @copydoc multiboot2::data::memory_map - */ - struct memory_map : vla_tag - { - using vla_tag::vla_tag; - - /** - * @brief The available memory regions - */ - [[nodiscard]] auto regions() const noexcept -> range_type - { - return {data(), size()}; - } - }; - - /** - * @copydoc multiboot2::data::module - */ - struct module : vla_tag - { - using vla_tag::vla_tag; - - /** - * @brief The module command line or name. - */ - [[nodiscard]] auto string() const noexcept -> std::string_view - { - return {data(), vla_tag::size()}; - } - - [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{end_address - start_address}; - } - }; - - struct acpi_rsdp : vla_tag - { - using vla_tag::vla_tag; - - [[nodiscard]] auto pointer() const noexcept -> range_type - { - return {data(), size()}; - } - }; - - struct acpi_xsdp : vla_tag - { - using vla_tag::vla_tag; - - [[nodiscard]] auto pointer() const noexcept -> range_type - { - return {data(), size()}; - } - }; - - struct information_view - { - using iterator = iterator; - using value_type = iterator::value_type; - using pointer = iterator::pointer; - using reference = iterator::reference; - - [[nodiscard]] auto size() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{m_size}; - } - - // Range access - - [[nodiscard]] auto begin() const noexcept -> iterator - { - return iterator{&m_tags}; - } - - [[nodiscard]] auto end() const noexcept -> iterator - { - return iterator{}; - } - - // Tag access - - template - [[nodiscard]] auto has() const noexcept -> bool - { - return get().has_value(); - } - - [[nodiscard]] auto maybe_basic_memory() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto basic_memory() const -> basic_memory - { - return maybe_basic_memory().value(); - } - - [[nodiscard]] auto maybe_bios_boot_device() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto bios_boot_device() const -> bios_boot_device - { - return maybe_bios_boot_device().value(); - } - - [[nodiscard]] auto maybe_command_line() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto command_line() const -> command_line - { - return maybe_command_line().value(); - } - - template - [[nodiscard]] auto maybe_elf_symbols() const noexcept -> std::optional> - { - return get>().and_then( - [](auto x) -> std::optional> { - if (x.entry_size_in_B == elf::section_header_size) - { - return std::optional{x}; - } - else - { - return std::nullopt; - } - }); - } - - template - [[nodiscard]] auto elf_symbols() const -> elf_symbols - { - return maybe_elf_symbols().value(); - } - - [[nodiscard]] auto maybe_loader_name() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto loader_name() const -> loader_name - { - return maybe_loader_name().value(); - } - - [[nodiscard]] auto maybe_memory_map() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto memory_map() const -> memory_map - { - return maybe_memory_map().value(); - } - - [[nodiscard]] auto modules() const noexcept - { - auto filter_modules = [](auto const & tag) { - return tag.information_id() == module::id; - }; - auto transform_module = [](auto const & tag) { - return module{&tag}; - }; - return std::ranges::subrange(begin(), end()) | std::views::filter(filter_modules) | - std::views::transform(transform_module); - } - - [[nodiscard]] auto maybe_acpi_rsdp() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto acpi_rsdp() const noexcept -> acpi_rsdp - { - return maybe_acpi_rsdp().value(); - } - - [[nodiscard]] auto maybe_acpi_xsdp() const noexcept -> std::optional - { - return get(); - } - - [[nodiscard]] auto acpi_xsdp() const noexcept -> acpi_xsdp - { - return maybe_acpi_xsdp().value(); - } - - private: - template - [[nodiscard]] constexpr auto get() const noexcept -> std::optional - { - if (auto found = std::ranges::find_if(*this, [](auto tag) { return tag.information_id() == Tag::id; }); - found != end()) - { - return Tag{&*found}; - } - return std::nullopt; - } - - uint32_t m_size{}; - uint32_t : 32; - tag_header m_tags{}; - }; - -} // namespace multiboot2 - -#endif diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/include/multiboot2/information/data.hpp deleted file mode 100644 index 315eb39..0000000 --- a/libs/multiboot2/include/multiboot2/information/data.hpp +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef MULTIBOOT2_INFORMATION_DATA_HPP -#define MULTIBOOT2_INFORMATION_DATA_HPP - -// IWYU pragma: private, include - -#include "multiboot2/constants/information_id.hpp" -#include "multiboot2/constants/memory_type.hpp" - -#include - -#include - -namespace multiboot2 -{ - //! A simple base mixin providing all data classes with an ID accessor. - template - struct tag_data - { - //! The ID of this data class. - constexpr auto static inline id = Id; - }; - - namespace data - { - - //! Basic system memory information - //! - //! This tag contains the amount of lower memory and upper memory present in the system as detected by the boot - //! loader. - struct basic_memory : tag_data - { - [[nodiscard]] constexpr auto lower() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{lower_KiB * 1024}; - } - - [[nodiscard]] constexpr auto upper() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{upper_KiB * 1024}; - } - - //! The amount of lower memory available to the system. - //! - //! Any memory below the 1 MiB address boundary is considered to be lower memory. The maximum possible value for - //! this field is 640 KiB. - std::uint32_t lower_KiB; - - //! The amount of upper memory available to the system. - //! - //! Any memory above the 1 MiB address boundary is considered to be upper memory. The maximum possible value for - //! this field is the address of the first upper memory hole minus 1MiB. - std::uint32_t upper_KiB; - }; - - //! The BIOS disk the image got loaded from - //! - //! This tag is present iff. the image was loaded from a BIOS disk device. - struct bios_boot_device : tag_data - { - //! BIOS device number the image was loaded from, as understood by INT 13h. - //! - //! For example, the first floppy drive will receive id 0x00, while the first hard disk will receive id 0x80. - std::uint32_t device_number; - - //! The partition number of the primary partition the image was loaded from. - std::uint32_t partition_number; - - //! The sub-partition number of the primary partition the image was loaded from. - std::uint32_t sub_partition_number; - }; - - //! The command line supplied to the image during boot. - //! - //! @note This structure is intentionally left blank, since it is of variable size and the contained information - //! starts at the first byte of this structure. - struct command_line : tag_data - { - }; - - //! The ELF sections of the image. - //! - //! @note The actual section header array is not part of this type's definition. The reason being that the size of - //! the array, in terms of entry count, as well as the size and format of each array element is unknown at compile - //! time. The array begins after the last member of this structure. - struct elf_symbols : tag_data - { - [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{entry_size_in_B}; - } - - //! The number of section header table entries. - std::uint32_t count; - - //! The size of each section header table entry. - std::uint32_t entry_size_in_B; - - //! The section number of the string table containing the section names. - std::uint32_t string_table_index; - }; - - //! The name of the boot loader that loaded the image. - //! - //! @note This structure is intentionally left blank, since it is of variable size and the contained information - //! starts at the first byte of this structure. - struct loader_name : tag_data - { - }; - - //! The detailed memory map of this system. - struct memory_map : tag_data - { - //! A single region of memory - struct region - { - //! Check if the memory described by this region is available for use. - [[nodiscard]] constexpr auto available() const noexcept - { - return type == memory_type::available; - } - - [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{size_in_B}; - } - - //! The physical start address of this region - std::uint64_t base; - - //! The size of this region in bytes. - std::uint64_t size_in_B; - - //! The type of this region. - //! - //! @see multiboot::memory_types - memory_type type; - - //! This field is reserved for padding and use in future extensions. - std::uint32_t : 0; - }; - - [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes - { - return kstd::units::bytes{entry_size_in_B}; - } - - //! The size of each entry present in the map. - //! - //! This field is present to allow for future extension of the entry format. Each entry's size is guaranteed to - //! always be an integer multiple of 8. - std::uint32_t entry_size_in_B; - - //! The version of each entry present in the map - std::uint32_t entry_version; - }; - - //! A module loaded by the bootloader. - //! - //! @note the command line associated with this module is not part of this structure, since it is of variable size - //! and the contained information starts at the end of this structure. - struct module : tag_data - { - //! The physical start address of this module. - std::uint32_t start_address; - - //! The physical end address of this module. - std::uint32_t end_address; - }; - - //! A copy of the ACPI RSDP - struct acpi_rsdp : tag_data - { - }; - - //! A copy of the ACPI XSDP - struct acpi_xsdp : tag_data - { - }; - - } // namespace data - -} // namespace multiboot2 - -#endif diff --git a/libs/multiboot2/include/multiboot2/information/iterator.hpp b/libs/multiboot2/include/multiboot2/information/iterator.hpp deleted file mode 100644 index 62c267d..0000000 --- a/libs/multiboot2/include/multiboot2/information/iterator.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef MULTIBOOT2_INFORMATION_ITERATOR_HPP -#define MULTIBOOT2_INFORMATION_ITERATOR_HPP - -// IWYU pragma: private, include - -#include "multiboot2/constants/information_id.hpp" -#include "tag.hpp" - -#include -#include -#include - -namespace multiboot2 -{ - - struct iterator - { - using iterator_category = std::forward_iterator_tag; - using value_type = tag_header; - using pointer = value_type const *; - using reference = value_type const &; - using difference_type = std::ptrdiff_t; - - constexpr iterator() = default; - - constexpr explicit iterator(tag_header const * offset) - : m_current(offset) - {} - - constexpr auto operator==(iterator const &) const noexcept -> bool = default; - - constexpr auto operator*() const noexcept -> reference - { - return *(m_current.value()); - } - - constexpr auto operator->() const noexcept -> pointer - { - return m_current.value(); - } - - constexpr auto operator++() noexcept -> iterator & - { - if (m_current) - { - if (auto next = m_current.value()->next(); next->information_id() != information_id::end) - { - m_current = next; - } - else - { - m_current.reset(); - } - } - return *this; - } - - constexpr auto operator++(int) noexcept -> iterator - { - auto copy = *this; - ++(*this); - return copy; - } - - private: - std::optional m_current{}; - }; - - static_assert(std::input_or_output_iterator); - -} // namespace multiboot2 - -#endif \ No newline at end of file diff --git a/libs/multiboot2/include/multiboot2/information/tag.hpp b/libs/multiboot2/include/multiboot2/information/tag.hpp deleted file mode 100644 index 8d90790..0000000 --- a/libs/multiboot2/include/multiboot2/information/tag.hpp +++ /dev/null @@ -1,206 +0,0 @@ -#ifndef MULTIBOOT2_INFORMATION_TAG_HPP -#define MULTIBOOT2_INFORMATION_TAG_HPP - -// IWYU pragma: private, include - -#include "multiboot2/constants/information_id.hpp" - -#include -#include -#include -#include - -namespace multiboot2 -{ - - /** - * @brief Header data and functionality shared by all tags. - */ - struct tag_header - { - tag_header() - : m_id{} - , m_size{} - {} - - tag_header(tag_header const * data) - : tag_header{*data} - {} - - [[nodiscard]] auto full_size() const noexcept -> std::size_t - { - return (m_size + 7) & (~7); - } - - [[nodiscard]] auto information_id() const noexcept -> information_id const & - { - return m_id; - } - - [[nodiscard]] auto next() const noexcept -> tag_header const * - { - return std::bit_cast(std::bit_cast(this) + full_size()); - } - - [[nodiscard]] auto unaligned_size() const noexcept -> std::uint32_t - { - return m_size; - } - - private: - enum information_id m_id; - std::uint32_t m_size; - }; - - /** - * @brief A tag containing no variable length array data. - */ - template - struct tag : tag_header, Data - { - tag() - : tag_header{} - , Data{} - {} - - explicit tag(tag_header const * header) - requires(sizeof(tag) > sizeof(tag_header)) - : tag_header{header} - , Data{*std::bit_cast(header + 1)} - {} - - explicit tag(tag_header const * header) - requires(sizeof(tag) == sizeof(tag_header)) - : tag_header{header} - , Data{} - {} - }; - - /** - * @brief A tag containing variable length array data. - */ - template typename Range> - struct vla_tag : tag - { - using range_type = Range; - - using value_type = range_type::value_type; - using reference = range_type::const_reference; - using const_reference = range_type::const_reference; - using pointer = range_type::const_pointer; - using const_pointer = range_type::const_pointer; - - using iterator = range_type::const_iterator; - using const_iterator = range_type::const_iterator; - using reverse_iterator = range_type::const_reverse_iterator; - using const_reverse_iterator = range_type::const_reverse_iterator; - using size_type = range_type::size_type; - using difference_type = range_type::difference_type; - - vla_tag() - : tag{} - , m_vla{} - {} - - explicit vla_tag(tag_header const * header) - : tag{header} - , m_vla{vla_start(header), vla_size(header)} - {} - - [[nodiscard]] auto begin() const noexcept -> const_iterator - { - return m_vla.begin(); - } - - [[nodiscard]] auto end() const noexcept -> const_iterator - { - return m_vla.end(); - } - - [[nodiscard]] auto cbegin() const noexcept -> const_iterator - { - return begin(); - } - - [[nodiscard]] auto cend() const noexcept -> const_iterator - { - return end(); - } - - [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator - { - return m_vla.rbegin(); - } - - [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator - { - return m_vla.rend(); - } - - [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator - { - return rbegin(); - } - - [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator - { - return rend(); - } - - [[nodiscard]] auto front() const noexcept -> const_reference - { - return m_vla.front(); - } - - [[nodiscard]] auto back() const noexcept -> const_reference - { - return m_vla.back(); - } - - [[nodiscard]] auto size() const noexcept -> std::size_t - { - return m_vla.size(); - } - - [[nodiscard]] auto empty() const noexcept -> bool - { - return m_vla.empty(); - } - - [[nodiscard]] auto data() const noexcept -> const_pointer - { - return m_vla.data(); - } - - [[nodiscard]] auto at(std::size_t index) const -> const_reference - { - return m_vla.at(index); - } - - [[nodiscard]] auto operator[](std::size_t index) const noexcept -> const_reference - { - return m_vla[index]; - } - - private: - auto static vla_start(tag_header const * header) noexcept -> VlaData * - { - auto raw = std::bit_cast(header); - auto start = raw + sizeof(tag); - return std::bit_cast(start); - } - - auto static vla_size(tag_header const * header) noexcept -> std::size_t - { - auto size = (header->unaligned_size() - sizeof(tag) - - std::is_same_v> * 1) / - sizeof(VlaData); - return size; - } - - range_type m_vla; - }; - -} // namespace multiboot2 - -#endif \ No newline at end of file diff --git a/libs/multiboot2/multiboot2/constants.hpp b/libs/multiboot2/multiboot2/constants.hpp new file mode 100644 index 0000000..2198210 --- /dev/null +++ b/libs/multiboot2/multiboot2/constants.hpp @@ -0,0 +1,15 @@ +#ifndef MULTIBOOT2_CONSTANTS_HPP +#define MULTIBOOT2_CONSTANTS_HPP + +#include "constants/architecture_id.hpp" // IWYU pragma: export + +#include + +namespace multiboot2 +{ + + constexpr auto inline header_magic = std::uint32_t{0xe852'50d6}; + +} // namespace multiboot2 + +#endif diff --git a/libs/multiboot2/multiboot2/constants/architecture_id.hpp b/libs/multiboot2/multiboot2/constants/architecture_id.hpp new file mode 100644 index 0000000..e13c471 --- /dev/null +++ b/libs/multiboot2/multiboot2/constants/architecture_id.hpp @@ -0,0 +1,22 @@ +#ifndef MULTIBOOT2_CONSTANTS_ARCHITECTURE_ID_HPP +#define MULTIBOOT2_CONSTANTS_ARCHITECTURE_ID_HPP + +// IWYU pragma: private, include + +#include + +namespace multiboot2 +{ + + //! The IDs of the supported system architectures. + enum struct architecture_id : std::uint32_t + { + //! 32-bit protected mode i386 + i386 = 0, + //! 32-bit MIPS + mips32 = 4, + }; + +} // namespace multiboot2 + +#endif \ No newline at end of file diff --git a/libs/multiboot2/multiboot2/constants/information_id.hpp b/libs/multiboot2/multiboot2/constants/information_id.hpp new file mode 100644 index 0000000..27c5300 --- /dev/null +++ b/libs/multiboot2/multiboot2/constants/information_id.hpp @@ -0,0 +1,86 @@ +#ifndef MULTIBOOT2_CONSTANTS_INFORMATION_ID_HPP +#define MULTIBOOT2_CONSTANTS_INFORMATION_ID_HPP + +// IWYU pragma: private, include + +#include + +namespace multiboot2 +{ + + //! The IDs of all Multiboot2 information tags. + //! + //! Each tag provided by the boot loader will be tagged with one of these IDs. A tags data format can thus be deduced + //! from it's ID. + enum struct information_id : std::uint32_t + { + //! Signals final tag for the multiboot2 information structure. + end, + + //! The command line string. + command_line, + + //! The name of the boot loader booting the kernel. + boot_loader_name, + + //! Indicates the boot module which was loaded along the kernel image. + module, + + //! The amount of lower and upper (above 1 MiB) memory. + basic_memory_information, + + //! Indicates which BIOS disk device the hoot loader has loaded the OS image from. + boot_device, + + //! Describes the memory layout of the system with individual areas and their flags. + memory_map, + + //! Includes information to access and utilize the device GPU. + vbe_information, + + //! VBE framebuffer information. + framebuferr_information, + + //! Includes list of all section headers from the loaded ELF kernel. + elf_sections, + + //! Advanced Power Management information. + apm_information, + + //! The pointer to the EFI 32 bit system table. + efi32_system_table_pointer, + + //! The pointer to the EFI 64 bit system table. + efi64_system_table_pointer, + + //! A copy of all System Management BIOS tables. + smbios_tables, + + //! A copy of RSDP as defined per ACPI 1.0 specification. + acpi_rsdp, + + //! A copy of XSDP as defined per ACPI 2.0 or later specification. + acpi_xsdp, + + //! The network information specified specified as per DHCP. + networking_information, + + //! The memory map as provided by the EFI. + efi_memory_map, + + //! Indicates ExitBootServices wasn't called. + boot_services_not_terminated, + + //! EFI 32 bit image handle pointer. + efi32_image_handle_pointer, + + //! EFI 64 bit image handle pointer. + efi64_image_handle_pointer, + + //! The physical image load base address. + image_load_base_address + }; + +} // namespace multiboot2 + +#endif diff --git a/libs/multiboot2/multiboot2/constants/memory_type.hpp b/libs/multiboot2/multiboot2/constants/memory_type.hpp new file mode 100644 index 0000000..6be94bd --- /dev/null +++ b/libs/multiboot2/multiboot2/constants/memory_type.hpp @@ -0,0 +1,33 @@ +#ifndef MULTIBOOT2_CONSTANTS_MEMORY_TYPE_HPP +#define MULTIBOOT2_CONSTANTS_MEMORY_TYPE_HPP + +// IWYU pragma: private, include + +#include + +namespace multiboot2 +{ + + //! The types of memory potentially present in the detailed memory map. + enum struct memory_type : std::uint32_t + { + //! The memory is available for general use by the operating system. + available = 1, + + //! The memory is reserved for firmware or other purposes and must not be used for general purposes by the operating + //! system. + reserved, + + //! The memory contains ACPI data and can be reclaimed when ACPI is not in use. + acpi_reclaimable, + + //! The memory is reserved for non-volatile storage. + non_volatile_storage, + + //! The memory range is occupied by defective RAM. + bad_ram, + }; + +} // namespace multiboot2 + +#endif \ No newline at end of file diff --git a/libs/multiboot2/multiboot2/constants/tag_id.hpp b/libs/multiboot2/multiboot2/constants/tag_id.hpp new file mode 100644 index 0000000..23d39cc --- /dev/null +++ b/libs/multiboot2/multiboot2/constants/tag_id.hpp @@ -0,0 +1,29 @@ +#ifndef MULTIBOOT2_CONSTANTS_TAG_ID_HPP +#define MULTIBOOT2_CONSTANTS_TAG_ID_HPP + +// IWYU pragma: private, include + +#include + +namespace multiboot2 +{ + + //! The IDs for tags to be used in the image header. + enum struct tag_id : std::uint32_t + { + end, + information_request, + addresses, + entry_address, + console_flags, + preferred_framebuffer_mode, + page_align_modules, + efi_boot_services_supported, + efi32_entry_address, + efi64_entry_address, + relocatable_image, + }; + +} // namespace multiboot2 + +#endif \ No newline at end of file diff --git a/libs/multiboot2/multiboot2/information.hpp b/libs/multiboot2/multiboot2/information.hpp new file mode 100644 index 0000000..a2ded56 --- /dev/null +++ b/libs/multiboot2/multiboot2/information.hpp @@ -0,0 +1,308 @@ +#ifndef MULTIBOOT2_INFORMATION_HPP +#define MULTIBOOT2_INFORMATION_HPP + +#include "information/data.hpp" // IWYU pragma: export +#include "information/iterator.hpp" // IWYU pragma: export +#include "information/tag.hpp" // IWYU pragma: export + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace multiboot2 +{ + + /** + * @copydoc multiboot2::data::basic_memory + */ + struct basic_memory : tag + { + using tag::tag; + }; + + /** + * @copydoc multiboot2::data::bios_boot_device + */ + struct bios_boot_device : tag + { + using tag::tag; + }; + + /** + * @copydoc multiboot2::data::command_line + */ + struct command_line : vla_tag + { + using vla_tag::vla_tag; + + /** + * @brief The command line string + */ + [[nodiscard]] auto string() const noexcept -> range_type + { + return {data(), size()}; + } + }; + + /** + * @copydoc multiboot2::data::elf_symbols + */ + template + struct elf_symbols : vla_tag const, std::span> + { + using base = vla_tag const, std::span>; + using base::base; + + [[nodiscard]] auto name(elf::section_header const & section) const noexcept -> std::string_view + { + if (!this->string_table_index) + { + std::abort(); + } + + auto string_table = this->begin()[this->string_table_index]; + auto name_offset = section.name_offset; + auto name_array = std::bit_cast(string_table.virtual_load_address); + return name_array + name_offset; + } + }; + + /** + * @copydoc multiboot2::data::loader_name + */ + struct loader_name : vla_tag + { + using vla_tag::vla_tag; + + /** + * @brief The name of the bootloader + */ + [[nodiscard]] auto string() const noexcept -> std::string_view + { + return {data(), size()}; + } + }; + + /** + * @copydoc multiboot2::data::memory_map + */ + struct memory_map : vla_tag + { + using vla_tag::vla_tag; + + /** + * @brief The available memory regions + */ + [[nodiscard]] auto regions() const noexcept -> range_type + { + return {data(), size()}; + } + }; + + /** + * @copydoc multiboot2::data::module + */ + struct module : vla_tag + { + using vla_tag::vla_tag; + + /** + * @brief The module command line or name. + */ + [[nodiscard]] auto string() const noexcept -> std::string_view + { + return {data(), vla_tag::size()}; + } + + [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{end_address - start_address}; + } + }; + + struct acpi_rsdp : vla_tag + { + using vla_tag::vla_tag; + + [[nodiscard]] auto pointer() const noexcept -> range_type + { + return {data(), size()}; + } + }; + + struct acpi_xsdp : vla_tag + { + using vla_tag::vla_tag; + + [[nodiscard]] auto pointer() const noexcept -> range_type + { + return {data(), size()}; + } + }; + + struct information_view + { + using iterator = iterator; + using value_type = iterator::value_type; + using pointer = iterator::pointer; + using reference = iterator::reference; + + [[nodiscard]] auto size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{m_size}; + } + + // Range access + + [[nodiscard]] auto begin() const noexcept -> iterator + { + return iterator{&m_tags}; + } + + [[nodiscard]] auto end() const noexcept -> iterator + { + return iterator{}; + } + + // Tag access + + template + [[nodiscard]] auto has() const noexcept -> bool + { + return get().has_value(); + } + + [[nodiscard]] auto maybe_basic_memory() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto basic_memory() const -> basic_memory + { + return maybe_basic_memory().value(); + } + + [[nodiscard]] auto maybe_bios_boot_device() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto bios_boot_device() const -> bios_boot_device + { + return maybe_bios_boot_device().value(); + } + + [[nodiscard]] auto maybe_command_line() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto command_line() const -> command_line + { + return maybe_command_line().value(); + } + + template + [[nodiscard]] auto maybe_elf_symbols() const noexcept -> std::optional> + { + return get>().and_then( + [](auto x) -> std::optional> { + if (x.entry_size_in_B == elf::section_header_size) + { + return std::optional{x}; + } + else + { + return std::nullopt; + } + }); + } + + template + [[nodiscard]] auto elf_symbols() const -> elf_symbols + { + return maybe_elf_symbols().value(); + } + + [[nodiscard]] auto maybe_loader_name() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto loader_name() const -> loader_name + { + return maybe_loader_name().value(); + } + + [[nodiscard]] auto maybe_memory_map() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto memory_map() const -> memory_map + { + return maybe_memory_map().value(); + } + + [[nodiscard]] auto modules() const noexcept + { + auto filter_modules = [](auto const & tag) { + return tag.information_id() == module::id; + }; + auto transform_module = [](auto const & tag) { + return module{&tag}; + }; + return std::ranges::subrange(begin(), end()) | std::views::filter(filter_modules) | + std::views::transform(transform_module); + } + + [[nodiscard]] auto maybe_acpi_rsdp() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto acpi_rsdp() const noexcept -> acpi_rsdp + { + return maybe_acpi_rsdp().value(); + } + + [[nodiscard]] auto maybe_acpi_xsdp() const noexcept -> std::optional + { + return get(); + } + + [[nodiscard]] auto acpi_xsdp() const noexcept -> acpi_xsdp + { + return maybe_acpi_xsdp().value(); + } + + private: + template + [[nodiscard]] constexpr auto get() const noexcept -> std::optional + { + if (auto found = std::ranges::find_if(*this, [](auto tag) { return tag.information_id() == Tag::id; }); + found != end()) + { + return Tag{&*found}; + } + return std::nullopt; + } + + uint32_t m_size{}; + uint32_t : 32; + tag_header m_tags{}; + }; + +} // namespace multiboot2 + +#endif diff --git a/libs/multiboot2/multiboot2/information/data.hpp b/libs/multiboot2/multiboot2/information/data.hpp new file mode 100644 index 0000000..315eb39 --- /dev/null +++ b/libs/multiboot2/multiboot2/information/data.hpp @@ -0,0 +1,184 @@ +#ifndef MULTIBOOT2_INFORMATION_DATA_HPP +#define MULTIBOOT2_INFORMATION_DATA_HPP + +// IWYU pragma: private, include + +#include "multiboot2/constants/information_id.hpp" +#include "multiboot2/constants/memory_type.hpp" + +#include + +#include + +namespace multiboot2 +{ + //! A simple base mixin providing all data classes with an ID accessor. + template + struct tag_data + { + //! The ID of this data class. + constexpr auto static inline id = Id; + }; + + namespace data + { + + //! Basic system memory information + //! + //! This tag contains the amount of lower memory and upper memory present in the system as detected by the boot + //! loader. + struct basic_memory : tag_data + { + [[nodiscard]] constexpr auto lower() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{lower_KiB * 1024}; + } + + [[nodiscard]] constexpr auto upper() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{upper_KiB * 1024}; + } + + //! The amount of lower memory available to the system. + //! + //! Any memory below the 1 MiB address boundary is considered to be lower memory. The maximum possible value for + //! this field is 640 KiB. + std::uint32_t lower_KiB; + + //! The amount of upper memory available to the system. + //! + //! Any memory above the 1 MiB address boundary is considered to be upper memory. The maximum possible value for + //! this field is the address of the first upper memory hole minus 1MiB. + std::uint32_t upper_KiB; + }; + + //! The BIOS disk the image got loaded from + //! + //! This tag is present iff. the image was loaded from a BIOS disk device. + struct bios_boot_device : tag_data + { + //! BIOS device number the image was loaded from, as understood by INT 13h. + //! + //! For example, the first floppy drive will receive id 0x00, while the first hard disk will receive id 0x80. + std::uint32_t device_number; + + //! The partition number of the primary partition the image was loaded from. + std::uint32_t partition_number; + + //! The sub-partition number of the primary partition the image was loaded from. + std::uint32_t sub_partition_number; + }; + + //! The command line supplied to the image during boot. + //! + //! @note This structure is intentionally left blank, since it is of variable size and the contained information + //! starts at the first byte of this structure. + struct command_line : tag_data + { + }; + + //! The ELF sections of the image. + //! + //! @note The actual section header array is not part of this type's definition. The reason being that the size of + //! the array, in terms of entry count, as well as the size and format of each array element is unknown at compile + //! time. The array begins after the last member of this structure. + struct elf_symbols : tag_data + { + [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{entry_size_in_B}; + } + + //! The number of section header table entries. + std::uint32_t count; + + //! The size of each section header table entry. + std::uint32_t entry_size_in_B; + + //! The section number of the string table containing the section names. + std::uint32_t string_table_index; + }; + + //! The name of the boot loader that loaded the image. + //! + //! @note This structure is intentionally left blank, since it is of variable size and the contained information + //! starts at the first byte of this structure. + struct loader_name : tag_data + { + }; + + //! The detailed memory map of this system. + struct memory_map : tag_data + { + //! A single region of memory + struct region + { + //! Check if the memory described by this region is available for use. + [[nodiscard]] constexpr auto available() const noexcept + { + return type == memory_type::available; + } + + [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{size_in_B}; + } + + //! The physical start address of this region + std::uint64_t base; + + //! The size of this region in bytes. + std::uint64_t size_in_B; + + //! The type of this region. + //! + //! @see multiboot::memory_types + memory_type type; + + //! This field is reserved for padding and use in future extensions. + std::uint32_t : 0; + }; + + [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes + { + return kstd::units::bytes{entry_size_in_B}; + } + + //! The size of each entry present in the map. + //! + //! This field is present to allow for future extension of the entry format. Each entry's size is guaranteed to + //! always be an integer multiple of 8. + std::uint32_t entry_size_in_B; + + //! The version of each entry present in the map + std::uint32_t entry_version; + }; + + //! A module loaded by the bootloader. + //! + //! @note the command line associated with this module is not part of this structure, since it is of variable size + //! and the contained information starts at the end of this structure. + struct module : tag_data + { + //! The physical start address of this module. + std::uint32_t start_address; + + //! The physical end address of this module. + std::uint32_t end_address; + }; + + //! A copy of the ACPI RSDP + struct acpi_rsdp : tag_data + { + }; + + //! A copy of the ACPI XSDP + struct acpi_xsdp : tag_data + { + }; + + } // namespace data + +} // namespace multiboot2 + +#endif diff --git a/libs/multiboot2/multiboot2/information/iterator.hpp b/libs/multiboot2/multiboot2/information/iterator.hpp new file mode 100644 index 0000000..62c267d --- /dev/null +++ b/libs/multiboot2/multiboot2/information/iterator.hpp @@ -0,0 +1,73 @@ +#ifndef MULTIBOOT2_INFORMATION_ITERATOR_HPP +#define MULTIBOOT2_INFORMATION_ITERATOR_HPP + +// IWYU pragma: private, include + +#include "multiboot2/constants/information_id.hpp" +#include "tag.hpp" + +#include +#include +#include + +namespace multiboot2 +{ + + struct iterator + { + using iterator_category = std::forward_iterator_tag; + using value_type = tag_header; + using pointer = value_type const *; + using reference = value_type const &; + using difference_type = std::ptrdiff_t; + + constexpr iterator() = default; + + constexpr explicit iterator(tag_header const * offset) + : m_current(offset) + {} + + constexpr auto operator==(iterator const &) const noexcept -> bool = default; + + constexpr auto operator*() const noexcept -> reference + { + return *(m_current.value()); + } + + constexpr auto operator->() const noexcept -> pointer + { + return m_current.value(); + } + + constexpr auto operator++() noexcept -> iterator & + { + if (m_current) + { + if (auto next = m_current.value()->next(); next->information_id() != information_id::end) + { + m_current = next; + } + else + { + m_current.reset(); + } + } + return *this; + } + + constexpr auto operator++(int) noexcept -> iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + private: + std::optional m_current{}; + }; + + static_assert(std::input_or_output_iterator); + +} // namespace multiboot2 + +#endif \ No newline at end of file diff --git a/libs/multiboot2/multiboot2/information/tag.hpp b/libs/multiboot2/multiboot2/information/tag.hpp new file mode 100644 index 0000000..8d90790 --- /dev/null +++ b/libs/multiboot2/multiboot2/information/tag.hpp @@ -0,0 +1,206 @@ +#ifndef MULTIBOOT2_INFORMATION_TAG_HPP +#define MULTIBOOT2_INFORMATION_TAG_HPP + +// IWYU pragma: private, include + +#include "multiboot2/constants/information_id.hpp" + +#include +#include +#include +#include + +namespace multiboot2 +{ + + /** + * @brief Header data and functionality shared by all tags. + */ + struct tag_header + { + tag_header() + : m_id{} + , m_size{} + {} + + tag_header(tag_header const * data) + : tag_header{*data} + {} + + [[nodiscard]] auto full_size() const noexcept -> std::size_t + { + return (m_size + 7) & (~7); + } + + [[nodiscard]] auto information_id() const noexcept -> information_id const & + { + return m_id; + } + + [[nodiscard]] auto next() const noexcept -> tag_header const * + { + return std::bit_cast(std::bit_cast(this) + full_size()); + } + + [[nodiscard]] auto unaligned_size() const noexcept -> std::uint32_t + { + return m_size; + } + + private: + enum information_id m_id; + std::uint32_t m_size; + }; + + /** + * @brief A tag containing no variable length array data. + */ + template + struct tag : tag_header, Data + { + tag() + : tag_header{} + , Data{} + {} + + explicit tag(tag_header const * header) + requires(sizeof(tag) > sizeof(tag_header)) + : tag_header{header} + , Data{*std::bit_cast(header + 1)} + {} + + explicit tag(tag_header const * header) + requires(sizeof(tag) == sizeof(tag_header)) + : tag_header{header} + , Data{} + {} + }; + + /** + * @brief A tag containing variable length array data. + */ + template typename Range> + struct vla_tag : tag + { + using range_type = Range; + + using value_type = range_type::value_type; + using reference = range_type::const_reference; + using const_reference = range_type::const_reference; + using pointer = range_type::const_pointer; + using const_pointer = range_type::const_pointer; + + using iterator = range_type::const_iterator; + using const_iterator = range_type::const_iterator; + using reverse_iterator = range_type::const_reverse_iterator; + using const_reverse_iterator = range_type::const_reverse_iterator; + using size_type = range_type::size_type; + using difference_type = range_type::difference_type; + + vla_tag() + : tag{} + , m_vla{} + {} + + explicit vla_tag(tag_header const * header) + : tag{header} + , m_vla{vla_start(header), vla_size(header)} + {} + + [[nodiscard]] auto begin() const noexcept -> const_iterator + { + return m_vla.begin(); + } + + [[nodiscard]] auto end() const noexcept -> const_iterator + { + return m_vla.end(); + } + + [[nodiscard]] auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + [[nodiscard]] auto cend() const noexcept -> const_iterator + { + return end(); + } + + [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator + { + return m_vla.rbegin(); + } + + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator + { + return m_vla.rend(); + } + + [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator + { + return rbegin(); + } + + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator + { + return rend(); + } + + [[nodiscard]] auto front() const noexcept -> const_reference + { + return m_vla.front(); + } + + [[nodiscard]] auto back() const noexcept -> const_reference + { + return m_vla.back(); + } + + [[nodiscard]] auto size() const noexcept -> std::size_t + { + return m_vla.size(); + } + + [[nodiscard]] auto empty() const noexcept -> bool + { + return m_vla.empty(); + } + + [[nodiscard]] auto data() const noexcept -> const_pointer + { + return m_vla.data(); + } + + [[nodiscard]] auto at(std::size_t index) const -> const_reference + { + return m_vla.at(index); + } + + [[nodiscard]] auto operator[](std::size_t index) const noexcept -> const_reference + { + return m_vla[index]; + } + + private: + auto static vla_start(tag_header const * header) noexcept -> VlaData * + { + auto raw = std::bit_cast(header); + auto start = raw + sizeof(tag); + return std::bit_cast(start); + } + + auto static vla_size(tag_header const * header) noexcept -> std::size_t + { + auto size = (header->unaligned_size() - sizeof(tag) - + std::is_same_v> * 1) / + sizeof(VlaData); + return size; + } + + range_type m_vla; + }; + +} // namespace multiboot2 + +#endif \ No newline at end of file -- cgit v1.2.3 From 2d8fed40bd0d0f8144783b6b344dc79944291b72 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 23 Apr 2026 13:31:17 +0200 Subject: chore: organize includes --- libs/acpi/acpi/common/table_header.cpp | 4 ++-- libs/acpi/acpi/common/table_header.test.cpp | 4 +++- libs/acpi/acpi/data/madt.cpp | 4 ++-- libs/acpi/acpi/data/madt.hpp | 8 ++++---- libs/acpi/acpi/data/madt.test.cpp | 6 ++++-- libs/acpi/acpi/data/rsdt.cpp | 5 +++-- libs/acpi/acpi/data/rsdt.test.cpp | 9 ++++++--- libs/acpi/acpi/data/xsdt.cpp | 5 +++-- libs/acpi/acpi/data/xsdt.test.cpp | 9 ++++++--- libs/acpi/acpi/pointers.cpp | 5 +++-- libs/acpi/acpi/pointers.test.cpp | 1 + libs/kstd/include/kstd/bits/format/context.hpp | 3 +-- libs/kstd/include/kstd/stack | 1 + libs/kstd/include/kstd/string | 7 +++---- libs/kstd/include/kstd/units | 5 ++--- libs/kstd/tests/src/flat_map.cpp | 1 + libs/kstd/tests/src/vector.cpp | 4 ++-- libs/multiboot2/multiboot2/information.hpp | 12 ++++++------ libs/multiboot2/multiboot2/information/data.hpp | 4 ++-- libs/multiboot2/multiboot2/information/iterator.hpp | 2 +- 20 files changed, 56 insertions(+), 43 deletions(-) (limited to 'libs') diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp index c69ff5d..6a7a4dc 100644 --- a/libs/acpi/acpi/common/table_header.cpp +++ b/libs/acpi/acpi/common/table_header.cpp @@ -1,8 +1,8 @@ +#include + #include #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 d6976b3..53cdb26 100644 --- a/libs/acpi/acpi/common/table_header.test.cpp +++ b/libs/acpi/acpi/common/table_header.test.cpp @@ -1,7 +1,9 @@ +#include + #include -#include #include + #include SCENARIO("Common table header parsing", "[common_table_header]") diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp index a8d4741..1a8b6d3 100644 --- a/libs/acpi/acpi/data/madt.cpp +++ b/libs/acpi/acpi/data/madt.cpp @@ -1,7 +1,7 @@ -#include - #include +#include + #include #include #include diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp index 8307826..b76daa4 100644 --- a/libs/acpi/acpi/data/madt.hpp +++ b/libs/acpi/acpi/data/madt.hpp @@ -3,14 +3,14 @@ // IWYU pragma: private, include -#include -#include -#include - #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 6795499..5d3b366 100644 --- a/libs/acpi/acpi/data/madt.test.cpp +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -1,11 +1,13 @@ +#include + #include -#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") diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp index fe108c7..80e209d 100644 --- a/libs/acpi/acpi/data/rsdt.cpp +++ b/libs/acpi/acpi/data/rsdt.cpp @@ -1,7 +1,8 @@ -#include +#include #include -#include + +#include #include #include diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp index 937dce0..a6dd416 100644 --- a/libs/acpi/acpi/data/rsdt.test.cpp +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -1,12 +1,15 @@ -#include +#include #include -#include + +#include + #include -#include #include +#include + SCENARIO("RSDT parsing", "[rsdt]") { GIVEN("The basic compiled RSDT containing 8 table pointers") diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp index b4202b8..b77aeab 100644 --- a/libs/acpi/acpi/data/xsdt.cpp +++ b/libs/acpi/acpi/data/xsdt.cpp @@ -1,7 +1,8 @@ -#include +#include #include -#include + +#include #include #include diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp index 7fb564c..cc18a66 100644 --- a/libs/acpi/acpi/data/xsdt.test.cpp +++ b/libs/acpi/acpi/data/xsdt.test.cpp @@ -1,12 +1,15 @@ -#include +#include #include -#include + +#include + #include -#include #include +#include + SCENARIO("XSDT parsing", "[xsdt]") { GIVEN("The basic compiled XSDT containing 8 table pointers") diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp index 45a42ce..2ac8d31 100644 --- a/libs/acpi/acpi/pointers.cpp +++ b/libs/acpi/acpi/pointers.cpp @@ -1,7 +1,8 @@ -#include +#include #include -#include + +#include #include #include diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp index 06ce1a4..d7b700d 100644 --- a/libs/acpi/acpi/pointers.test.cpp +++ b/libs/acpi/acpi/pointers.test.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 7f392a0..1883fc8 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -3,9 +3,8 @@ // IWYU pragma: private, include -#include "arg.hpp" +#include "kstd/bits/format/arg.hpp" #include "kstd/bits/format/output_buffer.hpp" - #include #include diff --git a/libs/kstd/include/kstd/stack b/libs/kstd/include/kstd/stack index 9750376..77e6bfd 100644 --- a/libs/kstd/include/kstd/stack +++ b/libs/kstd/include/kstd/stack @@ -2,6 +2,7 @@ #define KSTD_STACK_HPP #include "kstd/vector" + #include #include diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 58c4a08..e228a04 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -1,10 +1,9 @@ #ifndef KSTD_STRING_HPP #define KSTD_STRING_HPP -#include "kstd/bits/format/context.hpp" -#include "kstd/bits/format/formatter.hpp" -#include "kstd/bits/format/formatter/string_view.hpp" - +#include +#include +#include #include #include #include diff --git a/libs/kstd/include/kstd/units b/libs/kstd/include/kstd/units index bc7e1b9..df5eb37 100644 --- a/libs/kstd/include/kstd/units +++ b/libs/kstd/include/kstd/units @@ -15,9 +15,8 @@ namespace kstd using value_type = ValueType; constexpr basic_unit() noexcept - : value{} - { - } + : value{} + {} explicit constexpr basic_unit(value_type value) noexcept : value{value} diff --git a/libs/kstd/tests/src/flat_map.cpp b/libs/kstd/tests/src/flat_map.cpp index eb599af..2b793d9 100644 --- a/libs/kstd/tests/src/flat_map.cpp +++ b/libs/kstd/tests/src/flat_map.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 97460b4..415ca8e 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -1,8 +1,8 @@ -#include "kstd/tests/os_panic.hpp" +#include +#include "kstd/tests/os_panic.hpp" #include #include -#include #include diff --git a/libs/multiboot2/multiboot2/information.hpp b/libs/multiboot2/multiboot2/information.hpp index a2ded56..5f75fc8 100644 --- a/libs/multiboot2/multiboot2/information.hpp +++ b/libs/multiboot2/multiboot2/information.hpp @@ -1,14 +1,14 @@ #ifndef MULTIBOOT2_INFORMATION_HPP #define MULTIBOOT2_INFORMATION_HPP -#include "information/data.hpp" // IWYU pragma: export -#include "information/iterator.hpp" // IWYU pragma: export -#include "information/tag.hpp" // IWYU pragma: export +#include +#include #include -#include -#include +#include "multiboot2/information/data.hpp" // IWYU pragma: export +#include "multiboot2/information/iterator.hpp" // IWYU pragma: export +#include "multiboot2/information/tag.hpp" // IWYU pragma: export #include #include @@ -129,7 +129,7 @@ namespace multiboot2 return kstd::units::bytes{end_address - start_address}; } }; - + struct acpi_rsdp : vla_tag { using vla_tag::vla_tag; diff --git a/libs/multiboot2/multiboot2/information/data.hpp b/libs/multiboot2/multiboot2/information/data.hpp index 315eb39..531e2f3 100644 --- a/libs/multiboot2/multiboot2/information/data.hpp +++ b/libs/multiboot2/multiboot2/information/data.hpp @@ -3,11 +3,11 @@ // IWYU pragma: private, include +#include + #include "multiboot2/constants/information_id.hpp" #include "multiboot2/constants/memory_type.hpp" -#include - #include namespace multiboot2 diff --git a/libs/multiboot2/multiboot2/information/iterator.hpp b/libs/multiboot2/multiboot2/information/iterator.hpp index 62c267d..ec0f4a2 100644 --- a/libs/multiboot2/multiboot2/information/iterator.hpp +++ b/libs/multiboot2/multiboot2/information/iterator.hpp @@ -4,7 +4,7 @@ // IWYU pragma: private, include #include "multiboot2/constants/information_id.hpp" -#include "tag.hpp" +#include "multiboot2/information/tag.hpp" #include #include -- cgit v1.2.3 From f6f10575f75ac23d06e1d94f7861611503daa7af Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 23 Apr 2026 14:03:28 +0200 Subject: chore: banish relative includes --- libs/elf/include/elf/section_header.hpp | 2 +- libs/kstd/include/kstd/bits/format/arg.hpp | 4 +-- libs/kstd/include/kstd/bits/format/args.hpp | 8 ++--- libs/kstd/include/kstd/bits/format/context.hpp | 4 +-- libs/kstd/include/kstd/bits/format/error.hpp | 2 +- libs/kstd/include/kstd/bits/format/formatter.hpp | 6 ++-- .../include/kstd/bits/format/formatter/bool.hpp | 10 +++---- .../include/kstd/bits/format/formatter/byte.hpp | 6 ++-- .../include/kstd/bits/format/formatter/char.hpp | 12 ++++---- .../include/kstd/bits/format/formatter/cstring.hpp | 6 ++-- .../kstd/bits/format/formatter/integral.hpp | 10 +++---- .../kstd/bits/format/formatter/ordering.hpp | 8 ++--- .../include/kstd/bits/format/formatter/pointer.hpp | 10 +++---- .../include/kstd/bits/format/formatter/range.hpp | 2 +- .../kstd/bits/format/formatter/string_view.hpp | 8 ++--- .../include/kstd/bits/format/parse_context.hpp | 2 +- libs/kstd/include/kstd/bits/format/specifiers.hpp | 4 +-- libs/kstd/include/kstd/bits/format/string.hpp | 8 ++--- libs/kstd/include/kstd/bits/observer_ptr.hpp | 2 +- libs/kstd/include/kstd/format | 34 +++++++++++----------- libs/kstd/include/kstd/memory | 6 ++-- libs/kstd/include/kstd/os/print.hpp | 4 +-- libs/kstd/include/kstd/print | 5 ++-- libs/kstd/include/kstd/stack | 2 +- libs/kstd/src/libc/stdlib.cpp | 2 +- libs/kstd/src/mutex.cpp | 4 +-- libs/kstd/src/os/error.cpp | 2 +- libs/kstd/tests/src/os_panic.cpp | 2 +- libs/kstd/tests/src/vector.cpp | 2 +- libs/multiboot2/multiboot2/constants.hpp | 2 +- libs/multiboot2/multiboot2/information.hpp | 6 ++-- libs/multiboot2/multiboot2/information/data.hpp | 4 +-- .../multiboot2/multiboot2/information/iterator.hpp | 4 +-- libs/multiboot2/multiboot2/information/tag.hpp | 2 +- 34 files changed, 97 insertions(+), 98 deletions(-) (limited to 'libs') diff --git a/libs/elf/include/elf/section_header.hpp b/libs/elf/include/elf/section_header.hpp index 2b907cb..b1305ec 100644 --- a/libs/elf/include/elf/section_header.hpp +++ b/libs/elf/include/elf/section_header.hpp @@ -1,7 +1,7 @@ #ifndef ELF_SECTION_HEADER_HPP #define ELF_SECTION_HEADER_HPP -#include "format.hpp" +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp index a9a6ab5..e65b26f 100644 --- a/libs/kstd/include/kstd/bits/format/arg.hpp +++ b/libs/kstd/include/kstd/bits/format/arg.hpp @@ -3,8 +3,8 @@ // IWYU pragma: private, include -#include "error.hpp" -#include "fwd.hpp" +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp index d1586ac..e8e3114 100644 --- a/libs/kstd/include/kstd/bits/format/args.hpp +++ b/libs/kstd/include/kstd/bits/format/args.hpp @@ -3,10 +3,10 @@ // IWYU pragma: private, include -#include "arg.hpp" -#include "context.hpp" -#include "fwd.hpp" -#include "parse_context.hpp" +#include +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 1883fc8..c166ba9 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -3,8 +3,8 @@ // IWYU pragma: private, include -#include "kstd/bits/format/arg.hpp" -#include "kstd/bits/format/output_buffer.hpp" +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/error.hpp b/libs/kstd/include/kstd/bits/format/error.hpp index f0863eb..c0cb53d 100644 --- a/libs/kstd/include/kstd/bits/format/error.hpp +++ b/libs/kstd/include/kstd/bits/format/error.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_ERROR_HPP #define KSTD_BITS_FORMAT_ERROR_HPP -#include "kstd/os/error.hpp" +#include namespace kstd::bits::format { diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp index 096168d..eb28829 100644 --- a/libs/kstd/include/kstd/bits/format/formatter.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter.hpp @@ -3,9 +3,9 @@ // IWYU pragma: private, include -#include "context.hpp" -#include "error.hpp" -#include "parse_context.hpp" +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index e371cec..cc8d190 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -1,11 +1,11 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP #define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP -#include "../context.hpp" -#include "../error.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" -#include "../specifiers.hpp" +#include +#include +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp index 70d98f4..cc8aece 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp @@ -1,9 +1,9 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP #define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP -#include "../context.hpp" -#include "../formatter.hpp" -#include "integral.hpp" +#include +#include +#include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp index ddfefe5..92489a1 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/char.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/char.hpp @@ -1,12 +1,12 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP #define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP -#include "../context.hpp" -#include "../error.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" -#include "../specifiers.hpp" -#include "integral.hpp" +#include +#include +#include +#include +#include +#include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp index 9afb974..553c8ca 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp @@ -1,9 +1,9 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP #define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP -#include "../context.hpp" -#include "../formatter.hpp" -#include "string_view.hpp" +#include +#include +#include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index e5a234a..d17dc95 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -1,11 +1,11 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP #define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP -#include "../context.hpp" -#include "../error.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" -#include "../specifiers.hpp" +#include +#include +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp index 758285d..7832226 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp @@ -1,10 +1,10 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP #define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP -#include "../context.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" -#include "../specifiers.hpp" +#include +#include +#include +#include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp index fe75a2f..15f9a5b 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp @@ -1,11 +1,11 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP #define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP -#include "../context.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" -#include "../specifiers.hpp" -#include "integral.hpp" +#include +#include +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/range.hpp b/libs/kstd/include/kstd/bits/format/formatter/range.hpp index 54ee7fb..05af06f 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/range.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/range.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP #define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP -#include "../formatter.hpp" +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp index f5b698e..7d74579 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp @@ -1,10 +1,10 @@ #ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP #define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP -#include "../context.hpp" -#include "../error.hpp" -#include "../formatter.hpp" -#include "../parse_context.hpp" +#include +#include +#include +#include #include diff --git a/libs/kstd/include/kstd/bits/format/parse_context.hpp b/libs/kstd/include/kstd/bits/format/parse_context.hpp index 063263b..cab8d72 100644 --- a/libs/kstd/include/kstd/bits/format/parse_context.hpp +++ b/libs/kstd/include/kstd/bits/format/parse_context.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include "error.hpp" +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 18c6f66..211c95d 100644 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -3,8 +3,8 @@ // IWYU pragma: private -#include "error.hpp" -#include "parse_context.hpp" +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp index 40282e4..e7e4088 100644 --- a/libs/kstd/include/kstd/bits/format/string.hpp +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -3,10 +3,10 @@ // IWYU pragma: private, include -#include "context.hpp" -#include "error.hpp" -#include "formatter.hpp" -#include "parse_context.hpp" +#include +#include +#include +#include #include #include diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp index 1c5da15..2593d7a 100644 --- a/libs/kstd/include/kstd/bits/observer_ptr.hpp +++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include "kstd/os/error.hpp" +#include #include #include diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index 047ea5c..e04b79a 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -1,22 +1,22 @@ #ifndef KSTD_FORMAT_HPP #define KSTD_FORMAT_HPP -#include "bits/format/arg.hpp" // IWYU pragma: export -#include "bits/format/args.hpp" // IWYU pragma: export -#include "bits/format/context.hpp" // IWYU pragma: export -#include "bits/format/formatter.hpp" // IWYU pragma: export -#include "bits/format/formatter/bool.hpp" // IWYU pragma: export -#include "bits/format/formatter/byte.hpp" // IWYU pragma: export -#include "bits/format/formatter/char.hpp" // IWYU pragma: export -#include "bits/format/formatter/cstring.hpp" // IWYU pragma: export -#include "bits/format/formatter/integral.hpp" // IWYU pragma: export -#include "bits/format/formatter/ordering.hpp" // IWYU pragma: export -#include "bits/format/formatter/pointer.hpp" // IWYU pragma: export -#include "bits/format/formatter/range.hpp" // IWYU pragma: export -#include "bits/format/formatter/string_view.hpp" // IWYU pragma: export -#include "bits/format/output_buffer.hpp" // IWYU pragma: export -#include "bits/format/parse_context.hpp" // IWYU pragma: export -#include "bits/format/string.hpp" // IWYU pragma: export -#include "bits/format/vformat.hpp" // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory index 493f49a..f108c6d 100644 --- a/libs/kstd/include/kstd/memory +++ b/libs/kstd/include/kstd/memory @@ -1,8 +1,8 @@ #ifndef KSTD_MEMORY_HPP #define KSTD_MEMORY_HPP -#include "kstd/bits/observer_ptr.hpp" // IWYU pragma: export -#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export -#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp index b8e0732..36cb43d 100644 --- a/libs/kstd/include/kstd/os/print.hpp +++ b/libs/kstd/include/kstd/os/print.hpp @@ -1,8 +1,8 @@ #ifndef KSTD_OS_PRINT_HPP #define KSTD_OS_PRINT_HPP -#include "kstd/bits/format/args.hpp" -#include "kstd/bits/print_sink.hpp" +#include +#include #include diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print index f91cb04..1033f72 100644 --- a/libs/kstd/include/kstd/print +++ b/libs/kstd/include/kstd/print @@ -1,10 +1,9 @@ #ifndef KSTD_PRINT #define KSTD_PRINT -#include "bits/print_sink.hpp" // IWYU pragma: export -#include "os/print.hpp" - +#include // IWYU pragma: export #include +#include #include diff --git a/libs/kstd/include/kstd/stack b/libs/kstd/include/kstd/stack index 77e6bfd..02e44ea 100644 --- a/libs/kstd/include/kstd/stack +++ b/libs/kstd/include/kstd/stack @@ -1,7 +1,7 @@ #ifndef KSTD_STACK_HPP #define KSTD_STACK_HPP -#include "kstd/vector" +#include #include #include diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/src/libc/stdlib.cpp index bb40605..a18fed0 100644 --- a/libs/kstd/src/libc/stdlib.cpp +++ b/libs/kstd/src/libc/stdlib.cpp @@ -1,4 +1,4 @@ -#include "kstd/os/error.hpp" +#include namespace kstd::libc { diff --git a/libs/kstd/src/mutex.cpp b/libs/kstd/src/mutex.cpp index d66cb98..7387657 100644 --- a/libs/kstd/src/mutex.cpp +++ b/libs/kstd/src/mutex.cpp @@ -1,6 +1,6 @@ -#include "kstd/mutex" +#include -#include "kstd/os/error.hpp" +#include #include diff --git a/libs/kstd/src/os/error.cpp b/libs/kstd/src/os/error.cpp index b82158d..f969cb5 100644 --- a/libs/kstd/src/os/error.cpp +++ b/libs/kstd/src/os/error.cpp @@ -1,4 +1,4 @@ -#include "kstd/os/error.hpp" +#include namespace kstd::os { diff --git a/libs/kstd/tests/src/os_panic.cpp b/libs/kstd/tests/src/os_panic.cpp index 3eae6ff..0759763 100644 --- a/libs/kstd/tests/src/os_panic.cpp +++ b/libs/kstd/tests/src/os_panic.cpp @@ -1,4 +1,4 @@ -#include "kstd/tests/os_panic.hpp" +#include #include #include diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index 415ca8e..b838f42 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -1,7 +1,7 @@ #include -#include "kstd/tests/os_panic.hpp" #include +#include #include #include diff --git a/libs/multiboot2/multiboot2/constants.hpp b/libs/multiboot2/multiboot2/constants.hpp index 2198210..57fa940 100644 --- a/libs/multiboot2/multiboot2/constants.hpp +++ b/libs/multiboot2/multiboot2/constants.hpp @@ -1,7 +1,7 @@ #ifndef MULTIBOOT2_CONSTANTS_HPP #define MULTIBOOT2_CONSTANTS_HPP -#include "constants/architecture_id.hpp" // IWYU pragma: export +#include // IWYU pragma: export #include diff --git a/libs/multiboot2/multiboot2/information.hpp b/libs/multiboot2/multiboot2/information.hpp index 5f75fc8..f688fe5 100644 --- a/libs/multiboot2/multiboot2/information.hpp +++ b/libs/multiboot2/multiboot2/information.hpp @@ -6,9 +6,9 @@ #include -#include "multiboot2/information/data.hpp" // IWYU pragma: export -#include "multiboot2/information/iterator.hpp" // IWYU pragma: export -#include "multiboot2/information/tag.hpp" // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export #include #include diff --git a/libs/multiboot2/multiboot2/information/data.hpp b/libs/multiboot2/multiboot2/information/data.hpp index 531e2f3..f39a6cb 100644 --- a/libs/multiboot2/multiboot2/information/data.hpp +++ b/libs/multiboot2/multiboot2/information/data.hpp @@ -5,8 +5,8 @@ #include -#include "multiboot2/constants/information_id.hpp" -#include "multiboot2/constants/memory_type.hpp" +#include +#include #include diff --git a/libs/multiboot2/multiboot2/information/iterator.hpp b/libs/multiboot2/multiboot2/information/iterator.hpp index ec0f4a2..bded43e 100644 --- a/libs/multiboot2/multiboot2/information/iterator.hpp +++ b/libs/multiboot2/multiboot2/information/iterator.hpp @@ -3,8 +3,8 @@ // IWYU pragma: private, include -#include "multiboot2/constants/information_id.hpp" -#include "multiboot2/information/tag.hpp" +#include +#include #include #include diff --git a/libs/multiboot2/multiboot2/information/tag.hpp b/libs/multiboot2/multiboot2/information/tag.hpp index 8d90790..0c29299 100644 --- a/libs/multiboot2/multiboot2/information/tag.hpp +++ b/libs/multiboot2/multiboot2/information/tag.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include -#include "multiboot2/constants/information_id.hpp" +#include #include #include -- cgit v1.2.3 From e6c6bda14c9af0df9f4c185701b1e7939db6e1f9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 23 Apr 2026 17:12:31 +0200 Subject: elf: restructure according to p1204 --- libs/elf/CMakeLists.txt | 6 +- libs/elf/elf/format.hpp | 19 +++++ libs/elf/elf/section_header.hpp | 120 ++++++++++++++++++++++++++++++++ libs/elf/include/elf/format.hpp | 19 ----- libs/elf/include/elf/section_header.hpp | 120 -------------------------------- 5 files changed, 142 insertions(+), 142 deletions(-) create mode 100644 libs/elf/elf/format.hpp create mode 100644 libs/elf/elf/section_header.hpp delete mode 100644 libs/elf/include/elf/format.hpp delete mode 100644 libs/elf/include/elf/section_header.hpp (limited to 'libs') diff --git a/libs/elf/CMakeLists.txt b/libs/elf/CMakeLists.txt index f1f5275..22ca200 100644 --- a/libs/elf/CMakeLists.txt +++ b/libs/elf/CMakeLists.txt @@ -39,18 +39,18 @@ add_library("elf::lib" ALIAS "elf") file(GLOB_RECURSE ELF_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS - "include/**.hpp" + "elf/**.hpp" ) target_sources("elf" INTERFACE FILE_SET HEADERS - BASE_DIRS "include" + BASE_DIRS "elf" FILES ${ELF_HEADERS} ) target_include_directories("elf" INTERFACE - "include" + "${CMAKE_CURRENT_SOURCE_DIR}" ) set_target_properties("elf" PROPERTIES diff --git a/libs/elf/elf/format.hpp b/libs/elf/elf/format.hpp new file mode 100644 index 0000000..bc85101 --- /dev/null +++ b/libs/elf/elf/format.hpp @@ -0,0 +1,19 @@ +#ifndef ELF_FORMAT_HPP +#define ELF_FORMAT_HPP + +namespace elf +{ + + //! The format of the ELF file being processed. + //! + //! Certain information structures in the ELF are heavily dependent on the bitness of the target architecture. The + //! values of this enumeration type are used to distinguish between the different variants. + enum struct format + { + elf32, + elf64, + }; + +} // namespace elf + +#endif \ No newline at end of file diff --git a/libs/elf/elf/section_header.hpp b/libs/elf/elf/section_header.hpp new file mode 100644 index 0000000..b1305ec --- /dev/null +++ b/libs/elf/elf/section_header.hpp @@ -0,0 +1,120 @@ +#ifndef ELF_SECTION_HEADER_HPP +#define ELF_SECTION_HEADER_HPP + +#include + +#include +#include +#include +#include +#include + +namespace elf +{ + + //! The platform dependent header size. + //! + //! The size of a section header table in ELF is dependent on the bitness of the platform the file is designed for. + //! This constant allows compile-time parametrization of objects and functions for a specific architecture. + template + constexpr auto inline section_header_size = std::numeric_limits::max(); + + //! @copydoc elf::section_header_size + //! + //! @note This specialization provides the section header size for 32-bit ELF files. + template<> + constexpr auto inline section_header_size = 40uz; + + //! @copydoc elf::section_header_size + //! + //! @note This specialization provides the section header size for 64-bit ELF files. + template<> + constexpr auto inline section_header_size = 64uz; + + //! An ELF section header table entry. + //! + //! In the ELF, the section header table describes the layout and properties of the sections present in the loadable + //! files. This information is used to map and load data from the file according to their use. + template + struct section_header + { + //! A platform dependent unsigned integer. + //! + //! The size of certain fields in a section header of the ELF is dependent on the bitness of the target platform, + //! accounting for the differing sizes of 32 and 64 bit section header table entries. + using format_uint = std::conditional_t; + + //! The type of the section described by this header. + enum struct header_type : std::uint32_t + { + null = 0, ///< Is inactive + program_data = 1, ///< Contains program data + symbol_table = 2, ///< Contains a symbol table + string_table = 3, ///< Contains a string table + relocation_entries_with_addends = 4, ///< Contains relocation information with addends + hash_table = 5, ///< Contains a symbol hash table + dynamic_linking_entries = 6, ///< Contains dynamic linking information + notes = 7, ///< Contains additional notes about the object file + no_content = 8, ///< Contains no data + relocation_entries_without_addends = 9, ///< Contains relocation information without addends + reserved = 10, ///< Reserved for future use + dynamic_linker_symbol_table = 11, ///< Contains the dynamic linker symbol table + init_array = 14, ///< Contains an array of constructor pointers + fini_array = 15, ///< Contains an array of destructor pointers + preinit_array = 16, ///< Contains an array of pre-constructor pointers + group_table = 17, ///< Defines a section group + extended_section_header_indices = 18, ///< Contains extended section header indices + }; + + //! The properties of the section describe by this header. + enum struct header_flags : format_uint + { + writable = 0x1, ///< Contains writable data + allocated = 0x2, ///< Occupies memory during execution + executable = 0x4, ///< Contains executable instructions + mergeable = 0x10, ///< Contained data may be merged for deduplication + strings = 0x20, ///< Contains null-terminated strings + info_link = 0x40, ///< Contains the section header index of linked section + link_order = 0x80, ///< Must respect linking location relative to linked section + os_specific = 0x100, ///< Must be handled in an OS specific way + group_member = 0x200, ///< Is a member of a section group + thread_local_storage = 0x400, ///< Contains thread local storage data + compressed = 0x800, ///< Is compressed + }; + + //! Check if the section is allocated + [[nodiscard]] constexpr auto allocated() const noexcept -> bool + { + return std::to_underlying(flags) & std::to_underlying(header_flags::allocated); + } + + //! Check if the section is executable + [[nodiscard]] constexpr auto executable() const noexcept -> bool + { + return std::to_underlying(flags) & std::to_underlying(header_flags::executable); + } + + //! Check if the section is writable + [[nodiscard]] constexpr auto writable() const noexcept -> bool + { + return std::to_underlying(flags) & std::to_underlying(header_flags::writable); + } + + std::uint32_t name_offset; ///< Offset into the section header string table, defining the section name + header_type type; ///< Type of this section + header_flags flags; ///< Flags of this section + format_uint virtual_load_address; ///< Virtual address where this section is loaded + format_uint file_offset; ///< Offset of the start of this section's data in the file + format_uint size; ///< Size of this section in memory + std::uint32_t linked_section; ///< Index of a section this section is linked to + std::uint32_t extra_info; ///< Additional information for this section (type and flag dependent) + format_uint alignment; ///< Alignment requirement of this section in memory + format_uint entry_size; ///< Size of the entries inside this section (if any) + }; + + static_assert(sizeof(section_header) == section_header_size); + static_assert(sizeof(section_header) == section_header_size); + +} // namespace elf + +#endif \ No newline at end of file diff --git a/libs/elf/include/elf/format.hpp b/libs/elf/include/elf/format.hpp deleted file mode 100644 index bc85101..0000000 --- a/libs/elf/include/elf/format.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef ELF_FORMAT_HPP -#define ELF_FORMAT_HPP - -namespace elf -{ - - //! The format of the ELF file being processed. - //! - //! Certain information structures in the ELF are heavily dependent on the bitness of the target architecture. The - //! values of this enumeration type are used to distinguish between the different variants. - enum struct format - { - elf32, - elf64, - }; - -} // namespace elf - -#endif \ No newline at end of file diff --git a/libs/elf/include/elf/section_header.hpp b/libs/elf/include/elf/section_header.hpp deleted file mode 100644 index b1305ec..0000000 --- a/libs/elf/include/elf/section_header.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef ELF_SECTION_HEADER_HPP -#define ELF_SECTION_HEADER_HPP - -#include - -#include -#include -#include -#include -#include - -namespace elf -{ - - //! The platform dependent header size. - //! - //! The size of a section header table in ELF is dependent on the bitness of the platform the file is designed for. - //! This constant allows compile-time parametrization of objects and functions for a specific architecture. - template - constexpr auto inline section_header_size = std::numeric_limits::max(); - - //! @copydoc elf::section_header_size - //! - //! @note This specialization provides the section header size for 32-bit ELF files. - template<> - constexpr auto inline section_header_size = 40uz; - - //! @copydoc elf::section_header_size - //! - //! @note This specialization provides the section header size for 64-bit ELF files. - template<> - constexpr auto inline section_header_size = 64uz; - - //! An ELF section header table entry. - //! - //! In the ELF, the section header table describes the layout and properties of the sections present in the loadable - //! files. This information is used to map and load data from the file according to their use. - template - struct section_header - { - //! A platform dependent unsigned integer. - //! - //! The size of certain fields in a section header of the ELF is dependent on the bitness of the target platform, - //! accounting for the differing sizes of 32 and 64 bit section header table entries. - using format_uint = std::conditional_t; - - //! The type of the section described by this header. - enum struct header_type : std::uint32_t - { - null = 0, ///< Is inactive - program_data = 1, ///< Contains program data - symbol_table = 2, ///< Contains a symbol table - string_table = 3, ///< Contains a string table - relocation_entries_with_addends = 4, ///< Contains relocation information with addends - hash_table = 5, ///< Contains a symbol hash table - dynamic_linking_entries = 6, ///< Contains dynamic linking information - notes = 7, ///< Contains additional notes about the object file - no_content = 8, ///< Contains no data - relocation_entries_without_addends = 9, ///< Contains relocation information without addends - reserved = 10, ///< Reserved for future use - dynamic_linker_symbol_table = 11, ///< Contains the dynamic linker symbol table - init_array = 14, ///< Contains an array of constructor pointers - fini_array = 15, ///< Contains an array of destructor pointers - preinit_array = 16, ///< Contains an array of pre-constructor pointers - group_table = 17, ///< Defines a section group - extended_section_header_indices = 18, ///< Contains extended section header indices - }; - - //! The properties of the section describe by this header. - enum struct header_flags : format_uint - { - writable = 0x1, ///< Contains writable data - allocated = 0x2, ///< Occupies memory during execution - executable = 0x4, ///< Contains executable instructions - mergeable = 0x10, ///< Contained data may be merged for deduplication - strings = 0x20, ///< Contains null-terminated strings - info_link = 0x40, ///< Contains the section header index of linked section - link_order = 0x80, ///< Must respect linking location relative to linked section - os_specific = 0x100, ///< Must be handled in an OS specific way - group_member = 0x200, ///< Is a member of a section group - thread_local_storage = 0x400, ///< Contains thread local storage data - compressed = 0x800, ///< Is compressed - }; - - //! Check if the section is allocated - [[nodiscard]] constexpr auto allocated() const noexcept -> bool - { - return std::to_underlying(flags) & std::to_underlying(header_flags::allocated); - } - - //! Check if the section is executable - [[nodiscard]] constexpr auto executable() const noexcept -> bool - { - return std::to_underlying(flags) & std::to_underlying(header_flags::executable); - } - - //! Check if the section is writable - [[nodiscard]] constexpr auto writable() const noexcept -> bool - { - return std::to_underlying(flags) & std::to_underlying(header_flags::writable); - } - - std::uint32_t name_offset; ///< Offset into the section header string table, defining the section name - header_type type; ///< Type of this section - header_flags flags; ///< Flags of this section - format_uint virtual_load_address; ///< Virtual address where this section is loaded - format_uint file_offset; ///< Offset of the start of this section's data in the file - format_uint size; ///< Size of this section in memory - std::uint32_t linked_section; ///< Index of a section this section is linked to - std::uint32_t extra_info; ///< Additional information for this section (type and flag dependent) - format_uint alignment; ///< Alignment requirement of this section in memory - format_uint entry_size; ///< Size of the entries inside this section (if any) - }; - - static_assert(sizeof(section_header) == section_header_size); - static_assert(sizeof(section_header) == section_header_size); - -} // namespace elf - -#endif \ No newline at end of file -- cgit v1.2.3 From d906d70c94c2a40d5fc6fd26056c7bc57d540002 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 23 Apr 2026 17:16:34 +0200 Subject: acpi: move test data header --- libs/acpi/CMakeLists.txt | 4 ++-- libs/acpi/acpi/common/table_header.test.cpp | 4 ++-- libs/acpi/acpi/data/madt.test.cpp | 4 ++-- libs/acpi/acpi/data/rsdt.test.cpp | 3 +-- libs/acpi/acpi/data/xsdt.test.cpp | 3 +-- libs/acpi/acpi/test_data/tables.S | 17 +++++++++++++++++ libs/acpi/acpi/test_data/tables.hpp | 28 ++++++++++++++++++++++++++++ libs/acpi/test_data/tables.S | 17 ----------------- libs/acpi/test_data/tables.hpp | 28 ---------------------------- 9 files changed, 53 insertions(+), 55 deletions(-) create mode 100644 libs/acpi/acpi/test_data/tables.S create mode 100644 libs/acpi/acpi/test_data/tables.hpp delete mode 100644 libs/acpi/test_data/tables.S delete mode 100644 libs/acpi/test_data/tables.hpp (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index e73c6b3..d6d607a 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -95,7 +95,7 @@ if(BUILD_TESTING) list(APPEND GENERATED_TABLE_BLOBS "${CMAKE_CURRENT_BINARY_DIR}/test_data/${TABLE}.aml") endforeach() - set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") + set_source_files_properties("acpi/test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}") if(COMMAND "enable_coverage") enable_coverage("acpi") @@ -111,7 +111,7 @@ if(BUILD_TESTING) "acpi/data/xsdt.test.cpp" "acpi/pointers.test.cpp" - "test_data/tables.S" + "acpi/test_data/tables.S" ) target_include_directories("acpi_tests" PRIVATE diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp index 53cdb26..ddc879e 100644 --- a/libs/acpi/acpi/common/table_header.test.cpp +++ b/libs/acpi/acpi/common/table_header.test.cpp @@ -1,11 +1,11 @@ #include +#include + #include #include -#include - SCENARIO("Common table header parsing", "[common_table_header]") { GIVEN("A valid compiled table header") diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp index 5d3b366..1b95a74 100644 --- a/libs/acpi/acpi/data/madt.test.cpp +++ b/libs/acpi/acpi/data/madt.test.cpp @@ -1,13 +1,13 @@ #include +#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") diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp index a6dd416..47992ce 100644 --- a/libs/acpi/acpi/data/rsdt.test.cpp +++ b/libs/acpi/acpi/data/rsdt.test.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -8,8 +9,6 @@ #include -#include - SCENARIO("RSDT parsing", "[rsdt]") { GIVEN("The basic compiled RSDT containing 8 table pointers") diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp index cc18a66..77a5340 100644 --- a/libs/acpi/acpi/data/xsdt.test.cpp +++ b/libs/acpi/acpi/data/xsdt.test.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -8,8 +9,6 @@ #include -#include - SCENARIO("XSDT parsing", "[xsdt]") { GIVEN("The basic compiled XSDT containing 8 table pointers") diff --git a/libs/acpi/acpi/test_data/tables.S b/libs/acpi/acpi/test_data/tables.S new file mode 100644 index 0000000..641db6a --- /dev/null +++ b/libs/acpi/acpi/test_data/tables.S @@ -0,0 +1,17 @@ +.section .rodata + +.balign 16 + +#define TABLE(name, file) \ + .global name##_start; \ + .global name##_end; \ + name##_start: .incbin file; \ + name##_end: + +TABLE(basic_madt, "basic_madt.aml") +TABLE(basic_rsdt, "basic_rsdt.aml") +TABLE(basic_rsdp, "basic_rsdp.aml") +TABLE(basic_xsdt, "basic_xsdt.aml") +TABLE(table_header, "table_header.aml") + +#undef TABLE diff --git a/libs/acpi/acpi/test_data/tables.hpp b/libs/acpi/acpi/test_data/tables.hpp new file mode 100644 index 0000000..e91f1a5 --- /dev/null +++ b/libs/acpi/acpi/test_data/tables.hpp @@ -0,0 +1,28 @@ +#ifndef ACPI_TEST_DATA_TABLES_HPP +#define ACPI_TEST_DATA_TABLES_HPP + +#include +#include + +#define TABLE(name) \ + extern "C" std::byte const name##_start; \ + extern "C" std::byte const name##_end; \ + auto inline name()->std::span \ + { \ + return {&name##_start, &name##_end}; \ + } + +namespace acpi::test_data::tables +{ + + TABLE(basic_madt); + TABLE(basic_rsdt); + TABLE(basic_rsdp); + TABLE(basic_xsdt); + TABLE(table_header); + +} // namespace acpi::test_data::tables + +#undef TABLE + +#endif diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S deleted file mode 100644 index 641db6a..0000000 --- a/libs/acpi/test_data/tables.S +++ /dev/null @@ -1,17 +0,0 @@ -.section .rodata - -.balign 16 - -#define TABLE(name, file) \ - .global name##_start; \ - .global name##_end; \ - name##_start: .incbin file; \ - name##_end: - -TABLE(basic_madt, "basic_madt.aml") -TABLE(basic_rsdt, "basic_rsdt.aml") -TABLE(basic_rsdp, "basic_rsdp.aml") -TABLE(basic_xsdt, "basic_xsdt.aml") -TABLE(table_header, "table_header.aml") - -#undef TABLE diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp deleted file mode 100644 index e91f1a5..0000000 --- a/libs/acpi/test_data/tables.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef ACPI_TEST_DATA_TABLES_HPP -#define ACPI_TEST_DATA_TABLES_HPP - -#include -#include - -#define TABLE(name) \ - extern "C" std::byte const name##_start; \ - extern "C" std::byte const name##_end; \ - auto inline name()->std::span \ - { \ - return {&name##_start, &name##_end}; \ - } - -namespace acpi::test_data::tables -{ - - TABLE(basic_madt); - TABLE(basic_rsdt); - TABLE(basic_rsdp); - TABLE(basic_xsdt); - TABLE(table_header); - -} // namespace acpi::test_data::tables - -#undef TABLE - -#endif -- cgit v1.2.3 From bea3a1c32c0a034b201be2c30a69a5f8cdf81896 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 30 Apr 2026 18:58:26 +0200 Subject: kstd: align vector with standard --- libs/kstd/include/kstd/vector | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index e1b8b38..97fdffe 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -37,9 +37,9 @@ namespace kstd //! 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 = value_type *; + using pointer = std::allocator_traits::pointer; //! The type of pointers to constant elements in this vector. - using const_pointer = value_type const *; + 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. -- cgit v1.2.3 From 9ff0dffb026eae3b80e3e0b8bbb941e3e3b8b01f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 10:16:49 +0200 Subject: acpi: silence IASL compiler output --- libs/acpi/CMakeLists.txt | 7 ++++++- libs/acpi/cmake/Scripts/IaslCompile.cmake | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 libs/acpi/cmake/Scripts/IaslCompile.cmake (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index d6d607a..2c4d76d 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -87,7 +87,12 @@ if(BUILD_TESTING) foreach(TABLE IN LISTS TEST_TABLES) add_custom_command(OUTPUT "test_data/${TABLE}.aml" - COMMAND "${IASL_EXE}" -p "test_data/${TABLE}.aml" "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" + COMMAND ${CMAKE_COMMAND} + "-DIASL_EXE=${IASL_EXE}" + "-DIASL_OUTPUT=test_data/${TABLE}.aml" + "-DIASL_INPUT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" + "-P" + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Scripts/IaslCompile.cmake" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl" COMMENT "Compiling test_data/${TABLE}.asl" VERBATIM diff --git a/libs/acpi/cmake/Scripts/IaslCompile.cmake b/libs/acpi/cmake/Scripts/IaslCompile.cmake new file mode 100644 index 0000000..ff73b34 --- /dev/null +++ b/libs/acpi/cmake/Scripts/IaslCompile.cmake @@ -0,0 +1,16 @@ +execute_process( + COMMAND + "${IASL_EXE}" + "-vs" + "-p" + "${IASL_OUTPUT}" + "${IASL_INPUT}" + OUTPUT_VARIABLE IASL_OUT + ERROR_VARIABLE IASL_ERR + RESULT_VARIABLE IASL_RES +) + +if(NOT IASL_RES EQUAL 0) + message(STATUS "${IASL_OUT}") + message(FATAL_ERROR "${IASL_ERR}") +endif() -- cgit v1.2.3 From a958c515e4cfcd5572ac7e2c3a567e832630a12d Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 11:34:54 +0200 Subject: kstd/vector: implement append_range --- libs/kstd/include/kstd/bits/concepts.hpp | 15 +++++++ libs/kstd/include/kstd/vector | 69 +++++++++++++++++++++++++++++++- libs/kstd/tests/src/vector.cpp | 31 ++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/concepts.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/concepts.hpp b/libs/kstd/include/kstd/bits/concepts.hpp new file mode 100644 index 0000000..74c25cb --- /dev/null +++ b/libs/kstd/include/kstd/bits/concepts.hpp @@ -0,0 +1,15 @@ +#ifndef KSTD_BITS_CONCEPTS_HPP +#define KSTD_BITS_CONCEPTS_HPP + +#include +#include +namespace kstd::bits +{ + + template + concept container_compatible_range = + std::ranges::input_range && std::convertible_to, ValueType>; + +} + +#endif diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 97fdffe..2ecb6a8 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -2,6 +2,7 @@ #define KSTD_VECTOR_HPP #include +#include #include #include @@ -882,6 +883,68 @@ namespace kstd 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. @@ -950,7 +1013,8 @@ namespace kstd //! @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 - constexpr auto uninitialized_copy_with_allocator(const_iterator from, iterator to, size_type count) + template + constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count) { for (auto i = 0uz; i < count; ++i) { @@ -963,7 +1027,8 @@ namespace kstd //! @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 - constexpr auto uninitialized_move_with_allocator(iterator from, iterator to, size_type count) + template + constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count) { for (auto i = 0uz; i < count; ++i) { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index b838f42..bbbd3d1 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -516,6 +516,21 @@ SCENARIO("Vector modifiers", "[vector]") REQUIRE(it == v.begin()); } } + + WHEN("appending a range") + { + auto const range = std::views::iota(0, 3); + v.append_range(range); + + THEN("the size and capacity increase and the elements are appended") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + } + } } GIVEN("A populated vector") @@ -832,6 +847,22 @@ SCENARIO("Vector modifiers", "[vector]") REQUIRE(it == v.begin() + 1); } } + + WHEN("appending a range") + { + auto initial_size = v.size(); + v.append_range(std::views::iota(0, 3)); + + THEN("capacity and size are increased and the elements are appended") + { + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 1); + REQUIRE(v[initial_size + 2] == 2); + } + } + } } -- cgit v1.2.3 From 52b2fd214c8484a55c409c72c90929428e261949 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 11:35:10 +0200 Subject: kstd: vector add resize tests --- libs/kstd/tests/src/vector.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'libs') diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index bbbd3d1..edf47d7 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -531,6 +531,34 @@ SCENARIO("Vector modifiers", "[vector]") REQUIRE(v[2] == 2); } } + + 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); + } + } } GIVEN("A populated vector") @@ -863,6 +891,46 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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); + } + } } } -- cgit v1.2.3 From 338d9b2b6fc517df2135089699234232495324d6 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 13:11:37 +0200 Subject: kstd/vector: improve append_range tests --- libs/kstd/tests/include/kstd/tests/test_types.hpp | 27 +++++++- libs/kstd/tests/src/vector.cpp | 77 +++++++++++++++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) (limited to 'libs') diff --git a/libs/kstd/tests/include/kstd/tests/test_types.hpp b/libs/kstd/tests/include/kstd/tests/test_types.hpp index 9207ee9..8231fd3 100644 --- a/libs/kstd/tests/include/kstd/tests/test_types.hpp +++ b/libs/kstd/tests/include/kstd/tests/test_types.hpp @@ -239,17 +239,29 @@ namespace kstd::tests struct test_input_iterator { using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = int; + using reference = int const &; + using pointer = int const *; //! The current element pointed to by the iterator. int const * current; + //! The number of elements in the range. + std::size_t count; + + explicit test_input_iterator() + : current{nullptr} + , count{0} + {} //! Construct a new test input iterator. //! //! @param current The current element pointed to by the iterator. - constexpr explicit test_input_iterator(int const * current) + //! @param count The number of elements in the range. + explicit test_input_iterator(int const * current, std::size_t count) : current{current} + , count{count} {} //! Dereference the iterator to get the current element. @@ -266,6 +278,7 @@ namespace kstd::tests auto operator++() -> test_input_iterator & { ++current; + --count; return *this; } @@ -281,7 +294,17 @@ namespace kstd::tests //! @return True if the two test input iterators are equal, false otherwise. [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool { - return current == other.current; + if (current == nullptr && other.current == nullptr) + { + return true; + } + + if (current == nullptr || other.current == nullptr) + { + return count == other.count; + } + + return current == other.current && count == other.count; } }; diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index edf47d7..a02ebf0 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -522,16 +522,77 @@ SCENARIO("Vector modifiers", "[vector]") auto const range = std::views::iota(0, 3); v.append_range(range); - THEN("the size and capacity increase and the elements are appended") + 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); @@ -881,10 +942,18 @@ SCENARIO("Vector modifiers", "[vector]") auto initial_size = v.size(); v.append_range(std::views::iota(0, 3)); - THEN("capacity and size are increased and the elements are appended") + 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); @@ -1290,8 +1359,8 @@ SCENARIO("Vector advanced construction", "[vector]") WHEN("constructing from input iterators") { auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data()}; - auto const last = kstd::tests::test_input_iterator{arr.data() + arr.size()}; + 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); -- cgit v1.2.3 From 2773e457773e3c88c212d6403950cef1fc96f880 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 17:22:53 +0200 Subject: kstd/vector: implement insert_range --- libs/kstd/include/kstd/vector | 73 ++++++++++- libs/kstd/tests/src/vector.cpp | 291 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 362 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 2ecb6a8..c714957 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -147,8 +147,8 @@ namespace kstd //! //! template - requires(std::ranges::input_range && std::ranges::sized_range && - std::convertible_to, ValueType>) + 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)} @@ -729,6 +729,75 @@ namespace kstd return begin() + prefix_size; } + //! 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 { diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index a02ebf0..fd44437 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -620,6 +620,92 @@ SCENARIO("Vector modifiers", "[vector]") 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") @@ -1000,6 +1086,211 @@ SCENARIO("Vector modifiers", "[vector]") 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()); + } + } } } -- cgit v1.2.3 From 3ba99c5c8643be35a2337c120b226b17ddfcf58f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 1 May 2026 17:27:00 +0200 Subject: kstd/vector: add missing emplace test --- libs/kstd/tests/src/vector.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'libs') diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp index fd44437..4fb3559 100644 --- a/libs/kstd/tests/src/vector.cpp +++ b/libs/kstd/tests/src/vector.cpp @@ -761,6 +761,26 @@ SCENARIO("Vector modifiers", "[vector]") } } + 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(); -- cgit v1.2.3 From 3ab0a15fb6aba0ad9516da69589b9da8dbd63a8e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 2 May 2026 17:12:21 +0200 Subject: libs: adopt p1204 layout for kstd --- libs/kstd/CMakeLists.txt | 34 +- libs/kstd/include/kstd/allocator | 64 - libs/kstd/include/kstd/asm_ptr | 78 - libs/kstd/include/kstd/bits/concepts.hpp | 15 - libs/kstd/include/kstd/bits/flat_map.hpp | 186 -- libs/kstd/include/kstd/bits/format/arg.hpp | 75 - libs/kstd/include/kstd/bits/format/args.hpp | 160 -- libs/kstd/include/kstd/bits/format/context.hpp | 65 - libs/kstd/include/kstd/bits/format/error.hpp | 24 - libs/kstd/include/kstd/bits/format/formatter.hpp | 92 - .../include/kstd/bits/format/formatter/bool.hpp | 83 - .../include/kstd/bits/format/formatter/byte.hpp | 23 - .../include/kstd/bits/format/formatter/char.hpp | 94 - .../include/kstd/bits/format/formatter/cstring.hpp | 29 - .../kstd/bits/format/formatter/integral.hpp | 204 -- .../kstd/bits/format/formatter/ordering.hpp | 111 -- .../include/kstd/bits/format/formatter/pointer.hpp | 42 - .../include/kstd/bits/format/formatter/range.hpp | 32 - .../kstd/bits/format/formatter/string_view.hpp | 41 - libs/kstd/include/kstd/bits/format/fwd.hpp | 23 - .../include/kstd/bits/format/output_buffer.hpp | 32 - .../include/kstd/bits/format/parse_context.hpp | 109 -- libs/kstd/include/kstd/bits/format/specifiers.hpp | 205 -- libs/kstd/include/kstd/bits/format/string.hpp | 148 -- libs/kstd/include/kstd/bits/format/vformat.hpp | 99 - libs/kstd/include/kstd/bits/observer_ptr.hpp | 163 -- libs/kstd/include/kstd/bits/print_sink.hpp | 17 - libs/kstd/include/kstd/bits/shared_ptr.hpp | 648 ------- libs/kstd/include/kstd/bits/unique_ptr.hpp | 199 -- libs/kstd/include/kstd/cstring | 21 - libs/kstd/include/kstd/ext/bitfield_enum | 65 - libs/kstd/include/kstd/flat_map | 365 ---- libs/kstd/include/kstd/format | 22 - libs/kstd/include/kstd/memory | 8 - libs/kstd/include/kstd/mutex | 101 - libs/kstd/include/kstd/os/error.hpp | 34 - libs/kstd/include/kstd/os/print.hpp | 14 - libs/kstd/include/kstd/print | 69 - libs/kstd/include/kstd/ranges | 21 - libs/kstd/include/kstd/stack | 192 -- libs/kstd/include/kstd/string | 380 ---- libs/kstd/include/kstd/units | 149 -- libs/kstd/include/kstd/vector | 1154 ----------- libs/kstd/kstd/allocator | 64 + libs/kstd/kstd/asm_ptr | 78 + libs/kstd/kstd/bits/concepts.hpp | 15 + libs/kstd/kstd/bits/flat_map.hpp | 186 ++ libs/kstd/kstd/bits/format/arg.hpp | 75 + libs/kstd/kstd/bits/format/args.hpp | 160 ++ libs/kstd/kstd/bits/format/context.hpp | 65 + libs/kstd/kstd/bits/format/error.hpp | 24 + libs/kstd/kstd/bits/format/formatter.hpp | 92 + libs/kstd/kstd/bits/format/formatter/bool.hpp | 83 + libs/kstd/kstd/bits/format/formatter/byte.hpp | 23 + libs/kstd/kstd/bits/format/formatter/char.hpp | 94 + libs/kstd/kstd/bits/format/formatter/cstring.hpp | 29 + libs/kstd/kstd/bits/format/formatter/integral.hpp | 204 ++ libs/kstd/kstd/bits/format/formatter/ordering.hpp | 111 ++ libs/kstd/kstd/bits/format/formatter/pointer.hpp | 42 + libs/kstd/kstd/bits/format/formatter/range.hpp | 32 + .../kstd/bits/format/formatter/string_view.hpp | 41 + libs/kstd/kstd/bits/format/fwd.hpp | 23 + libs/kstd/kstd/bits/format/output_buffer.hpp | 32 + libs/kstd/kstd/bits/format/parse_context.hpp | 109 ++ libs/kstd/kstd/bits/format/specifiers.hpp | 205 ++ libs/kstd/kstd/bits/format/string.hpp | 148 ++ libs/kstd/kstd/bits/format/vformat.hpp | 99 + libs/kstd/kstd/bits/observer_ptr.hpp | 163 ++ libs/kstd/kstd/bits/observer_ptr.test.cpp | 360 ++++ libs/kstd/kstd/bits/print_sink.hpp | 17 + libs/kstd/kstd/bits/shared_ptr.hpp | 648 +++++++ libs/kstd/kstd/bits/unique_ptr.hpp | 199 ++ libs/kstd/kstd/cstring | 21 + libs/kstd/kstd/ext/bitfield_enum | 65 + libs/kstd/kstd/flat_map | 365 ++++ libs/kstd/kstd/flat_map.test.cpp | 314 +++ libs/kstd/kstd/format | 22 + libs/kstd/kstd/format.test.cpp | 114 ++ libs/kstd/kstd/libc/stdlib.cpp | 19 + libs/kstd/kstd/libc/string.cpp | 78 + libs/kstd/kstd/memory | 8 + libs/kstd/kstd/mutex | 101 + libs/kstd/kstd/mutex.cpp | 38 + libs/kstd/kstd/os/error.cpp | 12 + libs/kstd/kstd/os/error.hpp | 34 + libs/kstd/kstd/os/print.hpp | 14 + libs/kstd/kstd/print | 69 + libs/kstd/kstd/ranges | 21 + libs/kstd/kstd/stack | 192 ++ libs/kstd/kstd/string | 380 ++++ libs/kstd/kstd/string.test.cpp | 445 +++++ libs/kstd/kstd/test_support/os_panic.hpp | 23 + libs/kstd/kstd/test_support/os_panic.test.cpp | 15 + libs/kstd/kstd/test_support/test_types.hpp | 313 +++ libs/kstd/kstd/units | 149 ++ libs/kstd/kstd/vector | 1154 +++++++++++ libs/kstd/kstd/vector.test.cpp | 2003 ++++++++++++++++++++ libs/kstd/kstd/vformat.cpp | 209 ++ libs/kstd/src/libc/stdlib.cpp | 19 - libs/kstd/src/libc/string.cpp | 78 - libs/kstd/src/mutex.cpp | 38 - libs/kstd/src/os/error.cpp | 12 - libs/kstd/src/vformat.cpp | 209 -- libs/kstd/tests/include/kstd/tests/os_panic.hpp | 23 - libs/kstd/tests/include/kstd/tests/test_types.hpp | 313 --- libs/kstd/tests/src/flat_map.cpp | 314 --- libs/kstd/tests/src/format.cpp | 114 -- libs/kstd/tests/src/observer_ptr.cpp | 360 ---- libs/kstd/tests/src/os_panic.cpp | 15 - libs/kstd/tests/src/string.cpp | 445 ----- libs/kstd/tests/src/vector.cpp | 2003 -------------------- 111 files changed, 9615 insertions(+), 9617 deletions(-) delete mode 100644 libs/kstd/include/kstd/allocator delete mode 100644 libs/kstd/include/kstd/asm_ptr delete mode 100644 libs/kstd/include/kstd/bits/concepts.hpp delete mode 100644 libs/kstd/include/kstd/bits/flat_map.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/arg.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/args.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/error.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/bool.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/byte.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/char.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/cstring.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/integral.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/ordering.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/pointer.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/range.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/string_view.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/fwd.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/output_buffer.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/parse_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/string.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/vformat.hpp delete mode 100644 libs/kstd/include/kstd/bits/observer_ptr.hpp delete mode 100644 libs/kstd/include/kstd/bits/print_sink.hpp delete mode 100644 libs/kstd/include/kstd/bits/shared_ptr.hpp delete mode 100644 libs/kstd/include/kstd/bits/unique_ptr.hpp delete mode 100644 libs/kstd/include/kstd/cstring delete mode 100644 libs/kstd/include/kstd/ext/bitfield_enum delete mode 100644 libs/kstd/include/kstd/flat_map delete mode 100644 libs/kstd/include/kstd/format delete mode 100644 libs/kstd/include/kstd/memory delete mode 100644 libs/kstd/include/kstd/mutex delete mode 100644 libs/kstd/include/kstd/os/error.hpp delete mode 100644 libs/kstd/include/kstd/os/print.hpp delete mode 100644 libs/kstd/include/kstd/print delete mode 100644 libs/kstd/include/kstd/ranges delete mode 100644 libs/kstd/include/kstd/stack delete mode 100644 libs/kstd/include/kstd/string delete mode 100644 libs/kstd/include/kstd/units delete mode 100644 libs/kstd/include/kstd/vector create mode 100644 libs/kstd/kstd/allocator create mode 100644 libs/kstd/kstd/asm_ptr create mode 100644 libs/kstd/kstd/bits/concepts.hpp create mode 100644 libs/kstd/kstd/bits/flat_map.hpp create mode 100644 libs/kstd/kstd/bits/format/arg.hpp create mode 100644 libs/kstd/kstd/bits/format/args.hpp create mode 100644 libs/kstd/kstd/bits/format/context.hpp create mode 100644 libs/kstd/kstd/bits/format/error.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/bool.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/byte.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/char.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/cstring.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/integral.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/ordering.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/pointer.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/range.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/string_view.hpp create mode 100644 libs/kstd/kstd/bits/format/fwd.hpp create mode 100644 libs/kstd/kstd/bits/format/output_buffer.hpp create mode 100644 libs/kstd/kstd/bits/format/parse_context.hpp create mode 100644 libs/kstd/kstd/bits/format/specifiers.hpp create mode 100644 libs/kstd/kstd/bits/format/string.hpp create mode 100644 libs/kstd/kstd/bits/format/vformat.hpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.hpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.test.cpp create mode 100644 libs/kstd/kstd/bits/print_sink.hpp create mode 100644 libs/kstd/kstd/bits/shared_ptr.hpp create mode 100644 libs/kstd/kstd/bits/unique_ptr.hpp create mode 100644 libs/kstd/kstd/cstring create mode 100644 libs/kstd/kstd/ext/bitfield_enum create mode 100644 libs/kstd/kstd/flat_map create mode 100644 libs/kstd/kstd/flat_map.test.cpp create mode 100644 libs/kstd/kstd/format create mode 100644 libs/kstd/kstd/format.test.cpp create mode 100644 libs/kstd/kstd/libc/stdlib.cpp create mode 100644 libs/kstd/kstd/libc/string.cpp create mode 100644 libs/kstd/kstd/memory create mode 100644 libs/kstd/kstd/mutex create mode 100644 libs/kstd/kstd/mutex.cpp create mode 100644 libs/kstd/kstd/os/error.cpp create mode 100644 libs/kstd/kstd/os/error.hpp create mode 100644 libs/kstd/kstd/os/print.hpp create mode 100644 libs/kstd/kstd/print create mode 100644 libs/kstd/kstd/ranges create mode 100644 libs/kstd/kstd/stack create mode 100644 libs/kstd/kstd/string create mode 100644 libs/kstd/kstd/string.test.cpp create mode 100644 libs/kstd/kstd/test_support/os_panic.hpp create mode 100644 libs/kstd/kstd/test_support/os_panic.test.cpp create mode 100644 libs/kstd/kstd/test_support/test_types.hpp create mode 100644 libs/kstd/kstd/units create mode 100644 libs/kstd/kstd/vector create mode 100644 libs/kstd/kstd/vector.test.cpp create mode 100644 libs/kstd/kstd/vformat.cpp delete mode 100644 libs/kstd/src/libc/stdlib.cpp delete mode 100644 libs/kstd/src/libc/string.cpp delete mode 100644 libs/kstd/src/mutex.cpp delete mode 100644 libs/kstd/src/os/error.cpp delete mode 100644 libs/kstd/src/vformat.cpp delete mode 100644 libs/kstd/tests/include/kstd/tests/os_panic.hpp delete mode 100644 libs/kstd/tests/include/kstd/tests/test_types.hpp delete mode 100644 libs/kstd/tests/src/flat_map.cpp delete mode 100644 libs/kstd/tests/src/format.cpp delete mode 100644 libs/kstd/tests/src/observer_ptr.cpp delete mode 100644 libs/kstd/tests/src/os_panic.cpp delete mode 100644 libs/kstd/tests/src/string.cpp delete mode 100644 libs/kstd/tests/src/vector.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 2b5ee12..6902891 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -37,26 +37,28 @@ add_library("kstd" STATIC) add_library("kstd::lib" ALIAS "kstd") target_sources("kstd" PRIVATE - "src/os/error.cpp" - "src/mutex.cpp" - "src/vformat.cpp" + "kstd/os/error.cpp" + "kstd/mutex.cpp" + "kstd/vformat.cpp" ) file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS - "include/kstd/*" + "kstd/*" ) +list(FILTER KSTD_HEADERS EXCLUDE REGEX ".*\.cpp") + target_sources("kstd" PUBLIC FILE_SET HEADERS - BASE_DIRS "include" + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${KSTD_HEADERS} ) target_include_directories("kstd" PUBLIC - "include" + "${CMAKE_CURRENT_SOURCE_DIR}" ) set_target_properties("kstd" PROPERTIES @@ -65,8 +67,8 @@ set_target_properties("kstd" PROPERTIES if(NOT BUILD_TESTING) target_sources("kstd" PRIVATE - "src/libc/stdlib.cpp" - "src/libc/string.cpp" + "kstd/libc/stdlib.cpp" + "kstd/libc/string.cpp" ) set(KSTD_LIBC_SYMBOLS @@ -90,16 +92,12 @@ if(BUILD_TESTING) add_executable("kstd::tests" ALIAS "kstd_tests") target_sources("kstd_tests" PRIVATE - "tests/src/flat_map.cpp" - "tests/src/format.cpp" - "tests/src/vector.cpp" - "tests/src/observer_ptr.cpp" - "tests/src/os_panic.cpp" - "tests/src/string.cpp" - ) - - target_include_directories("kstd_tests" PRIVATE - "tests/include" + "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" ) target_link_libraries("kstd_tests" PRIVATE diff --git a/libs/kstd/include/kstd/allocator b/libs/kstd/include/kstd/allocator deleted file mode 100644 index 0de0e10..0000000 --- a/libs/kstd/include/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/include/kstd/asm_ptr b/libs/kstd/include/kstd/asm_ptr deleted file mode 100644 index c06a8b5..0000000 --- a/libs/kstd/include/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/include/kstd/bits/concepts.hpp b/libs/kstd/include/kstd/bits/concepts.hpp deleted file mode 100644 index 74c25cb..0000000 --- a/libs/kstd/include/kstd/bits/concepts.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef KSTD_BITS_CONCEPTS_HPP -#define KSTD_BITS_CONCEPTS_HPP - -#include -#include -namespace kstd::bits -{ - - template - concept container_compatible_range = - std::ranges::input_range && std::convertible_to, ValueType>; - -} - -#endif diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp deleted file mode 100644 index fe46203..0000000 --- a/libs/kstd/include/kstd/bits/flat_map.hpp +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef KSTD_BITS_FLAT_MAP_HPP -#define KSTD_BITS_FLAT_MAP_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd::bits -{ - - template - struct flat_map_reference - { - using key_type = KeyType; - using mapped_type = MappedType; - - constexpr flat_map_reference(key_type const & key, mapped_type & mapped) - : first{key} - , second{mapped} - {} - - constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const & - { - second = other.second; - return *this; - } - - constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const & - { - second = std::move(other.second); - return *this; - } - - template - requires(std::tuple_size_v> == 2) - constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const & - { - second = std::forward(tuple).second; - return *this; - } - - template - requires(Index >= 0 && Index <= 1) - [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto) - { - if constexpr (Index == 0) - { - return (first); - } - else - { - return (second); - } - } - - key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - }; - - template - struct flat_map_pointer - { - Reference reference; - - [[nodiscard]] constexpr auto operator->() noexcept -> Reference * - { - return std::addressof(reference); - } - - [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const * - { - return std::addressof(reference); - } - }; - - template - struct flat_map_iterator - { - using iterator_category = std::random_access_iterator_tag; - using value_type = std::pair; - using difference_type = std::ptrdiff_t; - using reference = flat_map_reference; - using pointer = flat_map_pointer; - - constexpr flat_map_iterator() = default; - - constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) - : m_key_iterator{key_iterator} - , m_mapped_iterator{mapped_iterator} - {} - - template - requires(std::convertible_to && - std::convertible_to) - constexpr flat_map_iterator( - flat_map_iterator const & other) noexcept - : m_key_iterator{other.m_key_iterator} - , m_mapped_iterator{other.m_mapped_iterator} - {} - - [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator - { - return m_key_iterator; - } - - [[nodiscard]] constexpr auto operator*() const noexcept -> reference - { - return {*m_key_iterator, *m_mapped_iterator}; - } - - [[nodiscard]] constexpr auto operator->() const noexcept -> pointer - { - return { - {*m_key_iterator, *m_mapped_iterator} - }; - } - - constexpr auto operator++() noexcept -> flat_map_iterator & - { - ++m_key_iterator; - ++m_mapped_iterator; - return *this; - } - - constexpr auto operator++(int) noexcept -> flat_map_iterator - { - auto copy = *this; - ++(*this); - return copy; - } - - constexpr auto operator--() noexcept -> flat_map_iterator & - { - --m_key_iterator; - --m_mapped_iterator; - return *this; - } - - constexpr auto operator--(int) noexcept -> flat_map_iterator - { - auto copy = *this; - --(*this); - return copy; - } - - [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator - { - return {m_key_iterator + offset, m_mapped_iterator + offset}; - } - - [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type - { - return m_key_iterator - other.m_key_iterator; - } - - [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default; - - private: - KeyIterator m_key_iterator{}; - MappedIterator m_mapped_iterator{}; - }; - -} // namespace kstd::bits - -template -struct std::tuple_size> : std::integral_constant -{ -}; - -template -struct std::tuple_element<0, kstd::bits::flat_map_reference> -{ - using type = K const &; -}; - -template -struct std::tuple_element<1, kstd::bits::flat_map_reference> -{ - using type = M &; -}; - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp deleted file mode 100644 index e65b26f..0000000 --- a/libs/kstd/include/kstd/bits/format/arg.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ARG_HPP -#define KSTD_BITS_FORMAT_ARG_HPP - -// IWYU pragma: private, include - -#include -#include - -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - enum struct arg_type : std::uint8_t - { - none, - boolean, - character, - integer, - unsigned_integer, - string_view, - c_string, - pointer, - user_defined, - }; - } // namespace bits::format - - struct format_arg - { - bits::format::arg_type type{}; - union - { - bool boolean; - char character; - std::int64_t integer; - std::uint64_t unsigned_integer; - std::string_view string_view; - char const * c_string; - void const * pointer; - struct - { - void const * pointer; - auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void; - } user_defined; - } value{}; - }; - - namespace bits::format - { - constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t - { - if (arg.type == arg_type::unsigned_integer) - { - return static_cast(arg.value.unsigned_integer); - } - else if (arg.type == arg_type::integer) - { - if (arg.value.integer < 0) - { - error("Dynamic width cannont be negative."); - } - return static_cast(arg.value.integer); - } - error("Dynamic width argument is not an integral value."); - return 0; - } - } // namespace bits::format - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp deleted file mode 100644 index e8e3114..0000000 --- a/libs/kstd/include/kstd/bits/format/args.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ARGS_HPP -#define KSTD_BITS_FORMAT_ARGS_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - - template - auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) - -> void - { - auto typed_value_pointer = static_cast(value_pointer); - auto fmt = formatter>{}; - auto const it = fmt.parse(parse_context); - parse_context.advance_to(it); - fmt.format(*typed_value_pointer, context); - } - - template - constexpr auto determine_arg_type() -> arg_type - { - using decay_type = std::remove_cvref_t; - if constexpr (std::same_as) - { - return arg_type::boolean; - } - else if constexpr (std::same_as) - { - return arg_type::character; - } - else if constexpr (std::integral && std::is_signed_v) - { - return arg_type::integer; - } - else if constexpr (std::integral && std::is_unsigned_v) - { - return arg_type::unsigned_integer; - } - else if constexpr (std::same_as) - { - return arg_type::string_view; - } - else if constexpr (std::same_as, char *> || - std::same_as, char const *>) - { - return arg_type::c_string; - } - else if constexpr (std::is_pointer_v || std::same_as) - { - if constexpr (std::same_as || std::same_as) - { - return arg_type::user_defined; - } - else - { - return arg_type::pointer; - } - } - else - { - return arg_type::user_defined; - } - } - - template - constexpr auto make_single_arg(ValueType const & value) -> format_arg - { - auto result = format_arg{}; - constexpr auto type = determine_arg_type(); - result.type = type; - - if constexpr (type == arg_type::boolean) - { - result.value.boolean = value; - } - else if constexpr (type == arg_type::character) - { - result.value.character = value; - } - else if constexpr (type == arg_type::integer) - { - result.value.integer = static_cast(value); - } - else if constexpr (type == arg_type::unsigned_integer) - { - result.value.unsigned_integer = static_cast(value); - } - else if constexpr (type == arg_type::string_view) - { - result.value.string_view = value; - } - else if constexpr (type == arg_type::c_string) - { - result.value.c_string = value; - } - else if constexpr (type == arg_type::pointer) - { - if constexpr (std::same_as, std::nullptr_t>) - { - result.value.pointer = nullptr; - } - else - { - result.value.pointer = static_cast(value); - } - } - else - { - result.value.user_defined.pointer = &value; - result.value.user_defined.format = format_trampoline; - } - return result; - } - - } // namespace bits::format - - using format_args = std::span; - - template - struct format_arg_store - { - std::array args{}; - }; - - template - [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store - { - using namespace bits::format; - - if constexpr (sizeof...(Arguments) == 0) - { - return format_arg_store<0>{}; - } - else - { - return format_arg_store{ - std::array{make_single_arg(args)...}}; - } - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp deleted file mode 100644 index c166ba9..0000000 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP -#define KSTD_BITS_FORMAT_CONTEXT_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - template - constexpr auto inline is_width_v = std::integral && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as; - } - - struct format_context - { - using format_args = std::span; - format_args args{}; - - format_context(bits::format::output_buffer & buffer, format_args args) - : args{args} - , m_buffer{&buffer} - {} - - [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & - { - if (id >= args.size()) - { - kstd::os::panic("[kstd:format] argument index out of range!"); - } - return args[id]; - } - - constexpr auto push(std::string_view string) -> void - { - m_buffer->push(string); - } - - constexpr auto push(char character) -> void - { - m_buffer->push(character); - } - - private: - bits::format::output_buffer * m_buffer; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/error.hpp b/libs/kstd/include/kstd/bits/format/error.hpp deleted file mode 100644 index c0cb53d..0000000 --- a/libs/kstd/include/kstd/bits/format/error.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ERROR_HPP -#define KSTD_BITS_FORMAT_ERROR_HPP - -#include - -namespace kstd::bits::format -{ - - constexpr auto error(char const * message) -> void - { - if consteval - { - extern void compile_time_format_error_triggered(char const *); - compile_time_format_error_triggered(message); - } - else - { - kstd::os::panic("Error while formatting a string."); - } - } - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp deleted file mode 100644 index eb28829..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef KSTD_BITS_FORMATTER_HPP -#define KSTD_BITS_FORMATTER_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -#include -#include -#include - -namespace kstd -{ - - template - struct formatter - { - formatter() = delete; - formatter(formatter const &) = delete; - auto operator=(formatter const &) -> formatter & = delete; - }; - - template - struct range_formatter - { - constexpr auto set_separator(std::string_view sep) -> void - { - m_separator = sep; - } - - constexpr auto set_brackets(std::string_view opening, std::string_view closing) - { - m_prefix = opening; - m_suffix = closing; - } - - constexpr auto parse(format_parse_context & context) - { - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it == 'n') - { - set_brackets("", ""); - std::advance(it, 1); - } - - if (it != end && *it == ':') - { - std::advance(it, 1); - context.advance_to(it); - it = m_inner_formatter.parse(context); - } - - if (it != end && *it != '}') - { - bits::format::error("Invalid formate specifier for range"); - } - - return it; - } - - template - auto format(Range const & range, format_context & context) - { - context.push(m_prefix); - - auto is_first = true; - std::ranges::for_each(range, [&](auto const & element) { - if (!is_first) - { - context.push(m_separator); - } - m_inner_formatter.format(element, context); - is_first = false; - }); - - context.push(m_suffix); - } - - private: - kstd::formatter m_inner_formatter{}; - std::string_view m_separator{", "}; - std::string_view m_prefix{"["}; - std::string_view m_suffix{"]"}; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp deleted file mode 100644 index cc8d190..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP -#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP - -#include -#include -#include -#include -#include - -#include -#include - -namespace kstd -{ - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 's') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for bool."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(bool value, format_context & context) const -> void - { - auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; - auto final_width = 0uz; - - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, - bits::format::alignment::left); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(text); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp deleted file mode 100644 index cc8aece..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP -#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP - -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - auto format(std::byte value, format_context & context) const -> void - { - formatter::format(static_cast(value), context); - } - }; - -} // namespace kstd -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp deleted file mode 100644 index 92489a1..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/char.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP -#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP - -#include -#include -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) - { - bits::format::error("Invalid format specifiers for 'char'"); - } - - if (it != end && *it != '}') - { - if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for char."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(char value, format_context & context) const -> void - { - if (specifiers.type == '\0' || specifiers.type == 'c') - { - auto final_width = 0uz; - - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - auto padding = - bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(value); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - else - { - formatter::format(static_cast(value), context); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp deleted file mode 100644 index 553c8ca..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP -#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP - -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - auto format(char const * string, format_context & context) const -> void - { - formatter::format(string ? std::string_view{string} : "(null)", context); - } - }; - - template<> - struct formatter : formatter - { - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp deleted file mode 100644 index d17dc95..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ /dev/null @@ -1,204 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP -#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto static maximum_digits = 80; - - enum struct base - { - bin = 2, - oct = 8, - dec = 10, - hex = 16, - }; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for integral type."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(T value, format_context & context) const -> void - { - auto final_width = 0uz; - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - using unsigned_T = std::make_unsigned_t; - auto absolute_value = static_cast(value); - auto is_negative = false; - - if constexpr (std::is_signed_v) - { - if (value < 0) - { - is_negative = true; - absolute_value = 0 - static_cast(value); - } - } - - auto const base = [type = specifiers.type] -> auto { - switch (type) - { - case 'x': - case 'X': - case 'p': - return base::hex; - case 'b': - case 'B': - return base::bin; - case 'o': - return base::oct; - default: - return base::dec; - } - }(); - - auto buffer = std::array{}; - auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; - auto current = buffer.rbegin(); - - if (absolute_value == 0) - { - *current = '0'; - std::advance(current, 1); - } - else - { - while (absolute_value != 0) - { - *current = digits[absolute_value % std::to_underlying(base)]; - std::advance(current, 1); - absolute_value /= std::to_underlying(base); - } - } - - auto content_length = static_cast(std::distance(buffer.rbegin(), current)); - auto prefix = std::array{'0', '\0'}; - auto prefix_length = 0uz; - if (specifiers.alternative_form) - { - switch (base) - { - case base::bin: - prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; - prefix_length = 2; - break; - case base::oct: - prefix_length = 1; - break; - case base::hex: - prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; - prefix_length = 2; - break; - default: - break; - } - } - - auto sign_character = '\0'; - if (is_negative) - { - sign_character = '-'; - } - else if (specifiers.sign == bits::format::sign_mode::plus) - { - sign_character = '+'; - } - else if (specifiers.sign == bits::format::sign_mode::space) - { - sign_character = ' '; - } - - auto const total_length = content_length + prefix_length + (sign_character != '\0'); - auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, - bits::format::alignment::right); - auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); - - if (!effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - } - - if (sign_character != '\0') - { - context.push(sign_character); - } - if (prefix_length > 0) - { - context.push(std::string_view{prefix.data(), prefix_length}); - } - - if (effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push('0'); - } - } - - context.push(std::string_view{current.base(), content_length}); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp deleted file mode 100644 index 7832226..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP -#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP - -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::strong_ordering value, format_context & context) const -> void - { - if (value == std::strong_ordering::equal) - { - return context.push(specifiers.alternative_form ? "==" : "equal"); - } - else if (value == std::strong_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::strong_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::strong_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid strong ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::weak_ordering value, format_context & context) const -> void - { - if (value == std::weak_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::weak_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::weak_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid weak ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::partial_ordering value, format_context & context) const -> void - { - if (value == std::partial_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::partial_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::partial_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - else if (value == std::partial_ordering::unordered) - { - return context.push(specifiers.alternative_form ? "<=>" : "unordered"); - } - kstd::os::panic("[kstd:format] Invalid partial ordering value!"); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp deleted file mode 100644 index 15f9a5b..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP -#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP - -#include -#include -#include -#include -#include - -#include -#include - -namespace kstd -{ - - template - struct formatter : formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto result = formatter::parse(context); - if (!this->specifiers.type) - { - this->specifiers.type = 'p'; - this->specifiers.alternative_form = true; - } - return result; - } - - auto format(T const * pointer, format_context & context) const -> void - { - formatter::format(std::bit_cast(pointer), context); - } - }; - - template - struct formatter : formatter - { - }; - -} // namespace kstd -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/range.hpp b/libs/kstd/include/kstd/bits/format/formatter/range.hpp deleted file mode 100644 index 05af06f..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/range.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP -#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP - -#include - -#include -#include -#include -#include - -namespace kstd -{ - namespace bits::format - { - template - concept iterable = requires(T const & t) { - t.begin(); - t.end(); - }; - - template - concept formattable_range = iterable && !std::same_as, std::string_view> && - !std::same_as, char *> && !std::same_as, char const *>; - } // namespace bits::format - - template - struct formatter : range_formatter> - { - }; -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp deleted file mode 100644 index 7d74579..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP -#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP - -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto it = context.begin(); - - if (it != context.end() && *it == 's') - { - ++it; - } - - if (it != context.end() && *it != '}') - { - bits::format::error("Invalid specifier for string_view."); - } - return it; - } - - auto format(std::string_view const & string, format_context & context) const -> void - { - context.push(string); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/fwd.hpp b/libs/kstd/include/kstd/bits/format/fwd.hpp deleted file mode 100644 index 6caedae..0000000 --- a/libs/kstd/include/kstd/bits/format/fwd.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FWD_HPP -#define KSTD_BITS_FORMAT_FWD_HPP - -// IWYU pragma: private - -#include - -namespace kstd -{ - - struct format_parse_context; - struct format_context; - struct format_arg; - - template - struct formatter; - - template - struct format_arg_store; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp deleted file mode 100644 index fd7a2b4..0000000 --- a/libs/kstd/include/kstd/bits/format/output_buffer.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP -#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP - -// IWYU pragma: private, include - -#include - -namespace kstd::bits::format -{ - - //! An abstract interface for formatted output buffers. - //! - //! This interface is intended to be use for functions dealing with string formatting, like the print and the format - //! family. - struct output_buffer - { - virtual ~output_buffer() = default; - - //! Push a text segment into the buffer. - //! - //! @param text The text segment to push. - virtual auto push(std::string_view text) -> void = 0; - - //! Push a single character into the buffer. - //! - //! @param character The character to push into the buffer. - virtual auto push(char character) -> void = 0; - }; - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/parse_context.hpp b/libs/kstd/include/kstd/bits/format/parse_context.hpp deleted file mode 100644 index cab8d72..0000000 --- a/libs/kstd/include/kstd/bits/format/parse_context.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP -#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP - -// IWYU pragma: private, include - -#include - -#include -#include - -namespace kstd -{ - - struct format_parse_context - { - using iterator = std::string_view::const_iterator; - - constexpr format_parse_context(std::string_view format, std::size_t argument_count, - bool const * is_integral = nullptr) - : m_current{format.begin()} - , m_end{format.end()} - , m_argument_count{argument_count} - , m_is_integral{is_integral} - {} - - [[nodiscard]] constexpr auto begin() const -> iterator - { - return m_current; - } - - [[nodiscard]] constexpr auto end() const -> iterator - { - return m_end; - } - - constexpr auto advance_to(iterator position) -> void - { - m_current = position; - } - - constexpr auto next_arg_id() -> std::size_t - { - if (m_mode == index_mode::manual) - { - bits::format::error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::automatic; - - if (m_next_argument_id >= m_argument_count) - { - bits::format::error("Argument index out of bounds."); - } - return m_next_argument_id++; - } - - constexpr auto check_arg_id(std::size_t index) -> void - { - if (m_mode == index_mode::automatic) - { - bits::format::error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::manual; - - if (index >= m_argument_count) - { - bits::format::error("Argument index out of bounds."); - } - } - - constexpr auto check_dynamic_width_id(std::size_t id) -> void - { - check_arg_id(id); - if (m_is_integral && !m_is_integral[id]) - { - bits::format::error("Dynamic width argument must be an integral object."); - } - } - - constexpr auto next_dynamic_width_id() -> std::size_t - { - auto const id = next_arg_id(); - if (m_is_integral && !m_is_integral[id]) - { - bits::format::error("Dynamic width argument must be an integral object."); - } - return id; - } - - private: - enum class index_mode - { - unknown, - automatic, - manual, - }; - - iterator m_current{}; - iterator m_end{}; - index_mode m_mode{}; - std::size_t m_next_argument_id{}; - std::size_t m_argument_count{}; - bool const * m_is_integral{}; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp deleted file mode 100644 index 211c95d..0000000 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_SPECS_HPP -#define KSTD_BITS_FORMAT_SPECS_HPP - -// IWYU pragma: private - -#include -#include - -#include -#include -#include - -namespace kstd::bits::format -{ - enum struct alignment : std::uint8_t - { - none, - left, - right, - center, - }; - - enum struct sign_mode : std::uint8_t - { - none, - plus, - minus, - space, - }; - - enum struct width_mode : std::uint8_t - { - none, - static_value, - dynamic_argument_id - }; - - struct specifiers - { - char fill{' '}; - alignment align{}; - sign_mode sign{}; - bool alternative_form{}; - bool zero_pad{}; - - width_mode mode{}; - std::size_t width_value{}; - char type{}; - }; - - struct padding - { - std::size_t left{}; - std::size_t right{}; - }; - - constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers - { - auto specs = specifiers{}; - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it == '}') - { - return specs; - } - - if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) - { - specs.fill = *it; - switch (*std::next(it)) - { - case '<': - specs.align = alignment::left; - break; - case '>': - specs.align = alignment::right; - break; - case '^': - default: - specs.align = alignment::center; - break; - } - std::advance(it, 2); - } - else if (*it == '<' || *it == '>' || *it == '^') - { - switch (*it) - { - case '<': - specs.align = alignment::left; - break; - case '>': - specs.align = alignment::right; - break; - case '^': - default: - specs.align = alignment::center; - break; - } - std::advance(it, 1); - } - - if (it != end && (*it == '+' || *it == '-' || *it == ' ')) - { - switch (*it) - { - case '+': - specs.sign = sign_mode::plus; - break; - case '-': - specs.sign = sign_mode::minus; - break; - case ' ': - default: - specs.sign = sign_mode::space; - break; - } - std::advance(it, 1); - } - - if (it != end && *it == '#') - { - specs.alternative_form = true; - std::advance(it, 1); - } - - if (it != end && *it == '0') - { - specs.zero_pad = true; - std::advance(it, 1); - } - - if (it != end && *it == '{') - { - specs.mode = width_mode::dynamic_argument_id; - std::advance(it, 1); - auto argument_id = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - context.check_dynamic_width_id(argument_id); - } - else - { - argument_id = context.next_dynamic_width_id(); - } - - if (it == end || *it != '}') - { - error("Expected '}' for dynamic width."); - } - std::advance(it, 1); - specs.width_value = argument_id; - } - else if (it != end && *it >= '0' && *it <= '9') - { - specs.mode = width_mode::static_value; - while (it != end && *it >= '0' && *it <= '9') - { - specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - } - - context.advance_to(it); - return specs; - } - - constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, - alignment requested_alignment, alignment default_alignment) -> padding - { - if (target_width <= content_length) - { - return {}; - } - - auto total_padding = target_width - content_length; - auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; - - switch (effective_alignment) - { - case alignment::center: - { - auto left = total_padding / 2; - auto right = total_padding - left; - return {left, right}; - } - case alignment::left: - return {0, total_padding}; - case alignment::right: - default: - return {total_padding, 0}; - break; - } - } - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp deleted file mode 100644 index e7e4088..0000000 --- a/libs/kstd/include/kstd/bits/format/string.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_STRING_HPP -#define KSTD_BITS_FORMAT_STRING_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - template - constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void - { - auto found = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index && !found) - { - using decay_type = std::remove_cvref_t; - static_assert(std::is_default_constructible_v>, "Missing formatter specialization."); - auto fmt = formatter{}; - - auto it = fmt.parse(context); - context.advance_to(it); - found = true; - } - ++current_index; - }()); - - if (!found) - { - error("Argument index out of bounds."); - } - } - - template - constexpr auto validate_dynamic_width(std::size_t target_index) -> void - { - auto is_valid_integer = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index) - { - using decay_type = std::remove_cvref_t; - if constexpr (std::is_integral_v) - { - is_valid_integer = true; - } - } - ++current_index; - }()); - - if (!is_valid_integer) - { - error("Dynamic width argument must be an integral object."); - } - } - } // namespace bits::format - - template - struct format_string - { - template - consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT - : str_view{str} - { - using namespace bits::format; - - auto const is_width_compatible = - std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; - auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; - auto it = context.begin(); - - while (it != context.end()) - { - if (*it == '{') - { - ++it; - if (it != context.end() && *it == '{') - { - ++it; - context.advance_to(it); - continue; - } - - context.advance_to(it); - auto argument_id = 0uz; - - if (it != context.end() && *it >= '0' && *it <= '9') - { - while (it != context.end() && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - ++it; - } - context.check_arg_id(argument_id); - context.advance_to(it); - } - else - { - argument_id = context.next_arg_id(); - } - - if (it != context.end() && *it == ':') - { - ++it; - context.advance_to(it); - } - - validate_argument(argument_id, context); - - it = context.begin(); - if (it == context.end() || *it != '}') - { - bits::format::error("Missing closing '}' in format string."); - } - } - else if (*it == '}') - { - ++it; - if (it != context.end() && *it == '}') - { - bits::format::error("Unescaped '}' in format string."); - } - } - ++it; - context.advance_to(it); - } - } - - std::string_view str_view; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp deleted file mode 100644 index 994fae5..0000000 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP -#define KSTD_BITS_FORMAT_VFORMAT_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - //! Format a string with the given arguments into the given buffer. - //! - //! External implementations of `vprint` may call this function. - //! - //! @param buffer The buffer to format into. - //! @param format The format string to use. - //! @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 - { - auto push(std::string_view text) -> void override; - auto push(char character) -> void override; - - auto release() -> string &&; - - private: - string m_result{}; - }; - - template Output> - struct iterator_writer : output_buffer - { - explicit iterator_writer(Output iterator) - : m_output{iterator} - {} - - [[nodiscard]] auto iterator() const -> Output - { - return m_output; - } - - auto push(std::string_view text) -> void override - { - m_output = std::ranges::copy(text, m_output).out; - } - - auto push(char character) -> void override - { - *m_output++ = character; - } - - private: - Output m_output{}; - }; - - } // namespace bits::format - - //! Format a given string with the provided arguments. - //! - //! @param format The format string. - //! @param args The arguments for the format string. - //! @return A new string containing the result of the format operation. - template - auto format(format_string...> format, ArgumentTypes &&... args) -> string - { - auto buffer = bits::format::string_writer{}; - bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); - return buffer.release(); - } - - //! Format a given string with the provided arguments. - //! - //! @param iterator The iterator to write to. - //! @param format The format string. - //! @param args The arguments for the format string. - //! @return An iterator past the last element written. - template Output, typename... ArgumentTypes> - auto format_to(Output iterator, format_string...> format, - ArgumentTypes &&... args) -> Output - { - auto buffer = bits::format::iterator_writer{iterator}; - bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); - return buffer.iterator(); - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp deleted file mode 100644 index 2593d7a..0000000 --- a/libs/kstd/include/kstd/bits/observer_ptr.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef KSTD_OBSERVER_PTR_HPP -#define KSTD_OBSERVER_PTR_HPP - -// IWYU pragma: private, include - -#include - -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct observer_ptr - { - //! The type of the element being pointed to. - using element_type = ElementType; - - //! Construct an empty observer pointer. - constexpr observer_ptr() noexcept = default; - - //! Construct an empty observer pointer from a null pointer. - constexpr observer_ptr(std::nullptr_t) noexcept {} - - //! Construct an observer pointer from a raw pointer. - constexpr explicit observer_ptr(element_type * pointer) - : m_ptr{pointer} - {} - - //! Construct an observer pointer from another observer pointer. - template - requires std::convertible_to - constexpr observer_ptr(observer_ptr other) noexcept - : m_ptr{other.get()} - {} - - //! Copy construct an observer pointer. - constexpr observer_ptr(observer_ptr const & other) noexcept = default; - - //! Move construct an observer pointer. - constexpr observer_ptr(observer_ptr && other) noexcept = default; - - //! Copy assign an observer pointer. - constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default; - - //! Move assign an observer pointer. - constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default; - - //! Stop watching the the watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto release() noexcept -> element_type * - { - return std::exchange(m_ptr, nullptr); - } - - //! Reset the observer pointer. - //! - //! @param pointer The new object to watch. - constexpr auto reset(element_type * pointer = nullptr) noexcept -> void - { - m_ptr = pointer; - } - - //! Swap the observer pointer with another observer pointer. - //! - //! @param other The other observer pointer to swap with. - constexpr auto swap(observer_ptr & other) noexcept -> void - { - std::swap(m_ptr, other.m_ptr); - } - - //! Get the currently watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto get() const noexcept -> element_type * - { - return m_ptr; - } - - //! Check if the observer pointer is watching an object. - //! - //! @return True if the observer pointer is watching an object, false otherwise. - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return m_ptr != nullptr; - } - - //! Get the currently watched object. - //! - //! @return A reference to the currently watched object. - [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t - { - throw_on_null(); - return *m_ptr; - } - - //! Get the currently watched object. - //! - //! @return A pointer to the currently watched object. - [[nodiscard]] constexpr auto operator->() const -> element_type * - { - throw_on_null(); - return m_ptr; - } - - //! Convert the observer pointer to a raw pointer. - //! - //! @return A pointer to the currently watched object. - constexpr explicit operator element_type *() const noexcept - { - return m_ptr; - } - - //! Compare the observer pointer with another observer pointer. - //!> - //! @param other The other observer pointer to compare with. - //! @return The result of the comparison. - constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; - - private: - //! Throw an exception if the observer pointer is null. - //! - //! @throws std::runtime_error if the observer pointer is null. - constexpr auto throw_on_null() const -> void - { - if (m_ptr == nullptr) - { - os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); - } - } - - //! The raw pointer to the watched object. - ElementType * m_ptr{}; - }; - - //! Swap two observer pointers. - //! - //! @param lhs The first observer pointer to swap. - //! @param rhs The second observer pointer to swap. - template - constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void - { - lhs.swap(rhs); - } - - //! Create an observer pointer from a raw pointer. - //! - //! @param pointer The raw pointer to create an observer pointer from. - //! @return An observer pointer to the given raw pointer. - template - constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr - { - return observer_ptr{pointer}; - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/print_sink.hpp b/libs/kstd/include/kstd/bits/print_sink.hpp deleted file mode 100644 index af765e0..0000000 --- a/libs/kstd/include/kstd/bits/print_sink.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef KSTD_BITS_PRINT_SINK_HPP -#define KSTD_BITS_PRINT_SINK_HPP - -// IWYU pragma: private, include - -namespace kstd -{ - - enum struct print_sink - { - stdout, - stderr, - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp deleted file mode 100644 index 8930095..0000000 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ /dev/null @@ -1,648 +0,0 @@ -#ifndef KSTD_BITS_SHARED_PTR_HPP -#define KSTD_BITS_SHARED_PTR_HPP - -#include -#include -#include -#include - -// IWYU pragma: private, include - -namespace kstd -{ - /** - * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared - * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed - * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. - * The weak_count is needed to determine when it is safe to delete the shared_control_block itself - */ - struct shared_control_block - { - std::atomic shared_count; - std::atomic weak_count; - - explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0) - : shared_count(shared) - , weak_count(weak) - {} - }; - - template - struct shared_ptr; - - /** - * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by - * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created - * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular - * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the - * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the - * weak_ptr becomes expired and cannot be used to access the object anymore. - */ - template - struct weak_ptr - { - template - friend struct shared_ptr; - - /** - * @brief Constructs a null weak_ptr. - */ - weak_ptr() noexcept - : pointer(nullptr) - , control(nullptr) - {} - - template - requires(std::is_convertible_v) - weak_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->weak_count); - } - } - - /** - * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. - */ - weak_ptr(weak_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->weak_count); - } - } - - /** - * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. - */ - weak_ptr(weak_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. - */ - auto operator=(weak_ptr const & other) -> weak_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - if (control != nullptr) - { - ++(control->weak_count); - } - } - - return *this; - } - - /** - * @brief Move assignment operator. Move-assigns a weak_ptr from other. - */ - auto operator=(weak_ptr && other) noexcept -> weak_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - } - - return *this; - } - - /** - * @brief Destructor. Cleans up resources if necessary. - */ - ~weak_ptr() - { - cleanup(); - } - - /** - * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an - * empty shared_ptr otherwise. - */ - [[nodiscard]] auto lock() const -> shared_ptr - { - return shared_ptr(*this); - } - - private: - auto cleanup() -> void - { - if (control != nullptr) - { - if (--(control->weak_count) == 0 && control->shared_count == 0) - { - delete control; - } - } - } - - T * pointer; - shared_control_block * control; - }; - - /** - * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to - * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to - * create shared_ptr instances in a member function of the object. - * - * @tparam T The type of the managed object. - */ - template - struct enable_shared_from_this - { - template - friend struct shared_ptr; - - friend T; - - public: - /** - * @brief Returns a shared_ptr that shares ownership of *this. - */ - auto shared_from_this() -> shared_ptr - { - return shared_ptr(weak_this); - } - - /** - * @brief Returns a shared_ptr that shares ownership of *this. - */ - auto shared_from_this() const -> shared_ptr - { - return shared_ptr(weak_this); - } - - private: - enable_shared_from_this() = default; - enable_shared_from_this(enable_shared_from_this const &) = default; - auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default; - ~enable_shared_from_this() = default; - - void internal_assign_ptr(shared_ptr const & ptr) const - { - weak_this = ptr; - } - - mutable weak_ptr weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality. - }; - - /** - * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several - * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of - * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining - * shared_ptr owning the object is assigned another pointer via operator= or reset(). A - * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used - * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), - * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count - * reaches zero. - * - * @tparam T The type of the managed object. - */ - template - struct shared_ptr - { - template - friend struct shared_ptr; - - template - friend struct weak_ptr; - - /** - * @brief Construct an empty shared_ptr. - */ - shared_ptr() noexcept - : pointer(nullptr) - , control(nullptr) - {} - - /** - * @brief Construct an empty shared_ptr from nullptr. - */ - shared_ptr(std::nullptr_t) noexcept - : pointer(nullptr) - , control(nullptr) - {} - - /** - * @brief Constructor. - * - * @param pointer A pointer to an object to manage (default is nullptr). - */ - template - requires(std::is_convertible_v) - explicit shared_ptr(U * pointer = nullptr) - : pointer(pointer) - , control(pointer != nullptr ? new shared_control_block() : nullptr) - { - assign_enable_shared_from_this(pointer); - } - - /** - * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares - * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr. - * - * @param other The weak_ptr to construct from. - */ - template - requires(std::is_convertible_v) - explicit shared_ptr(weak_ptr const & other) - : pointer(nullptr) - , control(nullptr) - { - if (other.control != nullptr && other.control->shared_count != 0) - { - pointer = other.pointer; - control = other.control; - ++(control->shared_count); - } - } - - /** - * @brief Copy constructor. - * - * @param other The shared_ptr to copy from. - */ - shared_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->shared_count); - } - } - - /** - * @brief Converting copy constructor for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other The shared_ptr to copy from. - */ - template - requires(std::is_convertible_v) - shared_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->shared_count); - } - } - - /** - * @brief Move constructor. - * - * @param other The shared_ptr to move from. - */ - shared_ptr(shared_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Converting move constructor for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other The shared_ptr to move from. - */ - template - requires(std::is_convertible_v) - shared_ptr(shared_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the - * object managed by r. If r manages no object, *this manages no object too. Equivalent to - * shared_ptr(r).swap(*this). - * - * @param other Another smart pointer to share the ownership with. - * @return Reference to this shared pointer. - */ - auto operator=(shared_ptr const & other) -> shared_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - - if (control != nullptr) - { - ++(control->shared_count); - } - } - - return *this; - } - - /** - * @brief Converting copy assignment for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other Another smart pointer to share ownership with. - * @return Reference to this shared pointer. - */ - template - requires(std::is_convertible_v) - auto operator=(shared_ptr const & other) -> shared_ptr & - { - cleanup(); - pointer = other.pointer; - control = other.control; - - if (control != nullptr) - { - ++(control->shared_count); - } - - return *this; - } - - /** - * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of - * the previous state of r, and r is empty. Equivalent to shared_ptr(std::move(r)).swap(*this). - * - * @param other Another smart pointer to acquire the ownership from. - * @return Reference to this shared pointer. - */ - auto operator=(shared_ptr && other) noexcept -> shared_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - } - - return *this; - } - - /** - * @brief Converting move assignment for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other Another smart pointer to acquire ownership from. - * @return Reference to this shared pointer. - */ - template - requires(std::is_convertible_v) - auto operator=(shared_ptr && other) noexcept -> shared_ptr & - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - - return *this; - } - - /** - * @brief Reset this shared_ptr to empty via nullptr assignment. - */ - auto operator=(std::nullptr_t) noexcept -> shared_ptr & - { - cleanup(); - pointer = nullptr; - control = nullptr; - return *this; - } - - /** - * @brief Destructor. Cleans up resources if necessary. - */ - ~shared_ptr() - { - cleanup(); - } - - /** - * @brief Replaces the managed object. - * - * @param ptr Pointer to a new object to manage (default = nullptr). - */ - void reset(T * ptr = nullptr) - { - cleanup(); - pointer = ptr; - control = ptr != nullptr ? new shared_control_block() : nullptr; - assign_enable_shared_from_this(ptr); - } - - /** - * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not - * adjusted. - * - * @param other The shared_ptr to swap with. - */ - void swap(shared_ptr & other) - { - std::swap(pointer, other.pointer); - std::swap(control, other.control); - } - - /** - * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. - * - * @return Returns the object owned by *this, equivalent to *get(). - */ - [[nodiscard]] auto operator*() const -> T & - { - return *pointer; - } - - /** - * @brief Member access operator. - * - * @return Returns a pointer to the object owned by *this, i.e. get(). - */ - [[nodiscard]] auto operator->() const -> T * - { - return pointer; - } - - /** - * @brief Returns a pointer to the managed object or nullptr if no object is owned. - * - * @return Pointer to the managed object or nullptr if no object is owned. - */ - [[nodiscard]] auto get() const -> T * - { - return pointer; - } - - /** - * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If - * there is no managed object, ​0​ is returned. - * - * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty - * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1, - * there are no other owners. - * - * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed - * object. - */ - [[nodiscard]] auto use_count() const -> std::size_t - { - if (control != nullptr) - { - return control->shared_count; - } - - return 0; - } - - /** - * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. - * - * @return true if *this owns an object, false otherwise. - */ - [[nodiscard]] explicit operator bool() const - { - return pointer != nullptr; - } - - /** - * @brief Compare shared_ptr with nullptr. - */ - [[nodiscard]] auto operator==(std::nullptr_t) const -> bool - { - return pointer == nullptr; - } - - /** - * @brief Compare nullptr with shared_ptr. - */ - [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool - { - return ptr.pointer == nullptr; - } - - private: - /** - * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this - * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the - * candidate type does not inherit from enable_shared_from_this, this function does nothing. - * - * @tparam U The candidate type to check for enable_shared_from_this inheritance. - * @param candidate The candidate object to assign the internal weak pointer for. - */ - template - auto assign_enable_shared_from_this(U * candidate) -> void - { - if constexpr (requires(U * p, shared_ptr const & sp) { p->internal_assign_ptr(sp); }) - { - if (candidate != nullptr) - { - candidate->internal_assign_ptr(*this); - } - } - } - - /** - * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object. - */ - auto cleanup() -> void - { - if (control != nullptr) - { - if (--(control->shared_count) == 0) - { - delete pointer; - pointer = nullptr; - - if (control->weak_count == 0) - { - delete control; - } - } - } - } - - T * pointer; ///< The managed object. - shared_control_block * control; ///< Shared control block. - }; - - /** - * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls - * lhs.swap(rhs). - * - * @tparam T Type of the managed object. - * @param lhs, rhs Smart pointers whose contents to swap. - */ - template - auto swap(shared_ptr & lhs, shared_ptr & rhs) -> void - { - lhs.swap(rhs); - } - - /** - * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The - * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is - * not an array type. The function is equivalent to: shared_ptr(new T(std::forward(args)...)). - * - * @tparam T Type of the managed object. - * @tparam Args Argument types for T's constructor. - * @param args List of arguments with which an instance of T will be constructed. - * @returns Shared_pointer of an instance of type T. - */ - template - auto make_shared(Args &&... args) -> shared_ptr - { - return shared_ptr(new T(std::forward(args)...)); - } - - /** - * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object - * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. - * @param lhs, rhs The shared_ptr instances to compare. - * @return true if lhs and rhs point to the same object, false otherwise. - */ - template - [[nodiscard]] auto inline operator==(shared_ptr const & lhs, shared_ptr const & rhs) -> bool - { - return lhs.get() == rhs.get(); - } - - /** - * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>. - * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. - * @param lhs, rhs The shared_ptr instances to compare. - * @return The result of comparing the stored pointers of lhs and rhs using operator<=> - */ - template - [[nodiscard]] auto inline operator<=>(shared_ptr const & lhs, shared_ptr const & rhs) - { - return lhs.get() <=> rhs.get(); - } -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/include/kstd/bits/unique_ptr.hpp deleted file mode 100644 index 3d803b4..0000000 --- a/libs/kstd/include/kstd/bits/unique_ptr.hpp +++ /dev/null @@ -1,199 +0,0 @@ -#ifndef KSTD_BITS_UNIQUE_POINTER_HPP -#define KSTD_BITS_UNIQUE_POINTER_HPP - -#include - -// IWYU pragma: private, include - -namespace kstd -{ - /** - * @brief Unique_pointer is a smart pointer that owns (is responsible for) and manages another object via a pointer - * and subsequently disposes of that object when the unique_ptr goes out of scope. - * - * @tparam T Type of the managed object. - */ - template - struct unique_ptr - { - template - friend struct unique_ptr; - - /** - * @brief Constructor. - * - * @param ptr A pointer to an object to manage (default is nullptr). - */ - explicit unique_ptr(T * ptr = nullptr) - : pointer(ptr) - { - // Nothing to do. - } - - /** - * @brief Destructor that deletes the managed object. - */ - ~unique_ptr() - { - delete pointer; - } - - /** - * @brief Deleted copy constructor to enforce unique ownership. - */ - unique_ptr(unique_ptr const &) = delete; - - template - requires(std::is_convertible_v) - unique_ptr(unique_ptr && other) noexcept - : pointer{std::exchange(other.pointer, nullptr)} - {} - - /** - * @brief Deleted copy assignment operator to enforce unique ownership. - */ - auto operator=(unique_ptr const &) -> unique_ptr & = delete; - - /** - * @brief Move constructor. - * - * @param other Unique pointer to move from. - */ - unique_ptr(unique_ptr && other) noexcept - : pointer{std::exchange(other.pointer, nullptr)} - {} - - /** - * @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()). - * - * @param other Smart pointer from which ownership will be transferred. - * @return Reference to this unique pointer. - */ - auto operator=(unique_ptr && other) noexcept -> unique_ptr & - { - if (this != &other) - { - delete pointer; - pointer = std::exchange(other.pointer, nullptr); - } - return *this; - } - - /** - * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. - * - * @return Returns the object owned by *this, equivalent to *get(). - */ - auto operator*() const -> T & - { - return *pointer; - } - - /** - * @brief Member access operator. - * - * @return Returns a pointer to the object owned by *this, i.e. get(). - */ - auto operator->() const -> T * - { - return pointer; - } - - /** - * @brief Returns a pointer to the managed object or nullptr if no object is owned. - * - * @return Pointer to the managed object or nullptr if no object is owned. - */ - [[nodiscard]] auto get() const -> T * - { - return pointer; - } - - /** - * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. - * - * @return true if *this owns an object, false otherwise. - */ - explicit operator bool() const noexcept - { - return pointer != nullptr; - } - - /** - * @brief Releases the ownership of the managed object, if any. - * get() returns nullptr after the call. - * The caller is responsible for cleaning up the object (e.g. by use of get_deleter()). - * - * @return Pointer to the managed object or nullptr if there was no managed object, i.e. the value which would be - * returned by get() before the call. - */ - auto release() -> T * - { - return std::exchange(pointer, nullptr); - } - - /** - * @brief Replaces the managed object. - * - * @note A test for self-reset, i.e. whether ptr points to an object already managed by *this, is not performed, - * except where provided as a compiler extension or as a debugging assert. Note that code such as - * p.reset(p.release()) does not involve self-reset, only code like p.reset(p.get()) does. - * - * @param ptr Pointer to a new object to manage (default = nullptr). - */ - auto reset(T * ptr = nullptr) -> void - { - delete std::exchange(pointer, ptr); - } - - /** - * @brief Swaps the managed objects and associated deleters of *this and another unique_ptr object other. - * - * @param other Another unique_ptr object to swap the managed object and the deleter with. - */ - auto swap(unique_ptr & other) -> void - { - using std::swap; - swap(pointer, other.pointer); - } - - /** - * @brief Defaulted three-way comparator operator. - */ - auto operator<=>(unique_ptr const & other) const = default; - - private: - T * pointer; ///< The managed pointer. - }; - - /** - * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls - * lhs.swap(rhs). - * - * @tparam T Type of the managed object. - * @param lhs, rhs Smart pointers whose contents to swap. - */ - template - auto swap(unique_ptr & lhs, unique_ptr & rhs) -> void - { - lhs.swap(rhs); - } - - /** - * @brief Constructs an object of type T and wraps it in a unique_ptr. Constructs a non-array type T. The - * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is - * not an array type. The function is equivalent to: unique_ptr(new T(std::forward(args)...)). - * - * @tparam T Type of the managed object. - * @tparam Args Argument types for T's constructor. - * @param args List of arguments with which an instance of T will be constructed. - * @returns Unique_pointer of an instance of type T. - */ - template - auto make_unique(Args &&... args) -> unique_ptr - { - return unique_ptr(new T(std::forward(args)...)); - } -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring deleted file mode 100644 index bd8b28d..0000000 --- a/libs/kstd/include/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/include/kstd/ext/bitfield_enum b/libs/kstd/include/kstd/ext/bitfield_enum deleted file mode 100644 index 80fe9d2..0000000 --- a/libs/kstd/include/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/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map deleted file mode 100644 index f12b1b5..0000000 --- a/libs/kstd/include/kstd/flat_map +++ /dev/null @@ -1,365 +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 - { - using key_container_type = KeyContainerType; - using mapped_container_type = MappedContainerType; - using key_type = KeyType; - using mapped_type = MappedType; - using value_type = std::pair; - 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} - {} - - //! 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(); - } - - private: - containers m_containers; - key_compare m_comparator; - }; -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format deleted file mode 100644 index e04b79a..0000000 --- a/libs/kstd/include/kstd/format +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef KSTD_FORMAT_HPP -#define KSTD_FORMAT_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory deleted file mode 100644 index f108c6d..0000000 --- a/libs/kstd/include/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/include/kstd/mutex b/libs/kstd/include/kstd/mutex deleted file mode 100644 index b2a31aa..0000000 --- a/libs/kstd/include/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/include/kstd/os/error.hpp b/libs/kstd/include/kstd/os/error.hpp deleted file mode 100644 index 9d43fb1..0000000 --- a/libs/kstd/include/kstd/os/error.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef KSTD_OS_ERROR_HPP -#define KSTD_OS_ERROR_HPP - -#include -#include - -namespace kstd::os -{ - /** - * @brief Handle an unrecoverable library error. - * - * The operating system kernel may choose to implement this function in order to try to perform additional cleanup - * before terminating execution. If the kernel does not implement this function, the default implementation will be - * chosen. This default implementation doest nothing. - */ - [[noreturn]] - auto abort() -> void; - - /** - * @brief Terminate execution of the operating system. - * - * The operating system must implement this function. This function must terminate the execution of the operating - * system kernel as is. It may choose to restart the kernel or to halt execution entirely. The implementation must - * guarantee that execution never return from this function. - * - * @param message A message describing the reason for termination of execution. - * @param where The source code location at which the panic was triggered. In general, no argument shall be provided - * for this parameter, thus implicitly capturing the location at which the call originates. - */ - [[noreturn]] - auto panic(std::string_view message, std::source_location where = std::source_location::current()) -> void; -} // namespace kstd::os - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp deleted file mode 100644 index 36cb43d..0000000 --- a/libs/kstd/include/kstd/os/print.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef KSTD_OS_PRINT_HPP -#define KSTD_OS_PRINT_HPP - -#include -#include - -#include - -namespace kstd::os -{ - auto vprint(print_sink sink, std::string_view format, kstd::format_args args) -> void; -} // namespace kstd::os - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print deleted file mode 100644 index 1033f72..0000000 --- a/libs/kstd/include/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/include/kstd/ranges b/libs/kstd/include/kstd/ranges deleted file mode 100644 index 78c3adb..0000000 --- a/libs/kstd/include/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/include/kstd/stack b/libs/kstd/include/kstd/stack deleted file mode 100644 index 02e44ea..0000000 --- a/libs/kstd/include/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/include/kstd/string b/libs/kstd/include/kstd/string deleted file mode 100644 index e228a04..0000000 --- a/libs/kstd/include/kstd/string +++ /dev/null @@ -1,380 +0,0 @@ -#ifndef KSTD_STRING_HPP -#define KSTD_STRING_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - /** - * @brief A simple string implementation that owns its data and provides basic operations. - */ - struct string - { - //! The type of the characters contained in this string. - using value_type = char; - //! The type of the underlying storage used by this string. - using storage_type = kstd::vector; - //! The type of all sizes used in and with this string. - 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 single values in this string. - using reference = value_type &; - //! The type of references to constant single values in this string. - using const_reference = value_type const &; - //! The type of pointers to single values in this string. - using pointer = value_type *; - //! The type of pointers to constant single values in this string. - using const_pointer = value_type const *; - //! The type of iterators into this string. - using iterator = pointer; - //! The type of constant iterators into this string. - using const_iterator = const_pointer; - - /** - * @brief Constructs an empty null-terminated string. - */ - string() - : m_storage{value_type{'\0'}} - {} - - /** - * @brief Constructs a string from a string view by copying the characters into owned storage. - * @param view The string view to copy the characters from. - */ - string(std::string_view view) - : string() - { - append(view); - } - - /** - * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. - * @param c_str The null-terminated C-style string to copy. - */ - string(char const * c_str) - : string() - { - if (c_str != nullptr) - { - append(std::string_view{c_str}); - } - } - - /** - * @brief Constructs a string containing a single character. - * @param c The character to copy. - */ - string(value_type c) - : string() - { - push_back(c); - } - - /** - * @brief Constructs a string by copying another string. - * @param other The string to copy. - */ - constexpr string(string const & other) - : m_storage{other.m_storage} - {} - - /** - * @brief Destructs the string. - */ - constexpr ~string() = default; - - /** - * @brief Assigns the value of another string to this string. - * @param other The string to assign from. - * @return A reference to this string. - */ - constexpr auto operator=(string const & other) -> string & = default; - - /** - * @brief Returns the number of characters in this string, not including the null terminator. - */ - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return m_storage.empty() ? 0 : m_storage.size() - 1; - } - - /** - * @brief Checks if this string is empty, not including the null terminator. - */ - [[nodiscard]] constexpr auto empty() const noexcept -> bool - { - return size() == 0; - } - - /** - * @brief Clears the content of the string, resulting in an empty string. - * The string remains null-terminated after this operation. - */ - constexpr auto clear() -> void - { - m_storage.clear(); - m_storage.push_back(value_type{'\0'}); - } - - //! Get a pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() noexcept -> pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() const noexcept -> const_pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer - { - return data(); - } - - //! Get an iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() noexcept -> iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator - { - return begin(); - } - - //! Get an iterator to the end of the string - [[nodiscard]] constexpr auto end() noexcept -> iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto end() const noexcept -> const_iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator - { - return end(); - } - - //! Get a reference to the first character of the string - [[nodiscard]] constexpr auto front() -> reference - { - return m_storage.front(); - } - - //! Get a const reference to the first character of the string - [[nodiscard]] constexpr auto front() const -> const_reference - { - return m_storage.front(); - } - - //! Get a reference to the last character of the string - [[nodiscard]] constexpr auto back() -> reference - { - return m_storage[size() - 1]; - } - - //! Get a const reference to the last character of the string - [[nodiscard]] constexpr auto back() const -> const_reference - { - return m_storage[size() - 1]; - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - */ - constexpr auto push_back(value_type ch) -> void - { - m_storage.back() = ch; - m_storage.push_back(value_type{'\0'}); - } - - /** - * @brief Appends a string view to the end of the string by copying the characters into owned storage. - * @param view The string view to append. - * @return A reference to this string. - */ - constexpr auto append(std::string_view view) -> string & - { - if (!view.empty()) - { - std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); - } - - return *this; - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto append(string const & other) -> string & - { - return append(other.view()); - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto operator+=(string const & other) -> string & - { - return append(other); - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - * @return A reference to this string. - */ - constexpr auto operator+=(value_type ch) -> string & - { - push_back(ch); - return *this; - } - - /** - * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. - */ - [[nodiscard]] constexpr auto view() const noexcept -> std::string_view - { - return std::string_view{data(), size()}; - } - - private: - //! The underlying storage of the string, which owns the characters and ensures null-termination. - storage_type m_storage{}; - }; - - /** - * @brief Concatenates a strings and a character and returns the result as a new string. - * @param lhs The string to concatenate. - * @param rhs The string to concatenate. - * @return A new string that is the result of concatenating @p lhs and @p rhs. - */ - [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string - { - string result{lhs}; - result += rhs; - return result; - } - - /** - * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and - * concatenating them. - * @tparam N 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 - requires std::unsigned_integral - [[nodiscard]] constexpr auto inline to_string(N value) -> string - { - if (value == 0) - { - return "0"; - } - - string result; - - while (value > 0) - { - char const digit = '0' + (value % 10); - result.push_back(digit); - value /= 10; - } - - std::reverse(result.begin(), result.end()); - return result; - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool - { - return lhs.view() == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool - { - return lhs.view() == rhs; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool - { - return lhs == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool - { - return lhs.view() == std::string_view{rhs}; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool - { - return std::string_view{lhs} == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - template<> - struct formatter : formatter - { - auto format(string const & str, format_context & context) const -> void - { - formatter::format(str.view(), context); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/units b/libs/kstd/include/kstd/units deleted file mode 100644 index df5eb37..0000000 --- a/libs/kstd/include/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/include/kstd/vector b/libs/kstd/include/kstd/vector deleted file mode 100644 index c714957..0000000 --- a/libs/kstd/include/kstd/vector +++ /dev/null @@ -1,1154 +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."); - } - - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - - //! 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; - } - - auto new_data = allocate_n(m_size); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, old_size); - clear_and_deallocate(); - std::exchange(m_data, new_data); - m_capacity = old_size; - m_size = old_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 - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(value); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - 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, value); - 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; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = value; - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value; - } - - ++m_size; - return begin() + prefix_size; - } - - //! 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 - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(std::move(value)); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - 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::move(value)); - 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; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = std::move(value); - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value); - } - - ++m_size; - return begin() + prefix_size; - } - - //! 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(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - emplace_back(std::forward(args)...); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - 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; - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value_type{std::forward(args)...}; - } - - ++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 - { - if (m_capacity == m_size) - { - 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 + m_size, value); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), value); - } - ++m_size; - } - - //! Append a given element to this vector via move construction. - constexpr auto push_back(value_type && value) -> void - { - if (m_capacity == m_size) - { - 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 + m_size, std::move(value)); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); - } - ++m_size; - } - - //! Append a given element to this vector via direct construction. - template - constexpr auto emplace_back(Args &&... args) -> reference - { - if (m_capacity == m_size) - { - 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 + m_size, std::forward(args)...); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - 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; - } - } - - //! 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++)); - } - } - - //! 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/allocator b/libs/kstd/kstd/allocator new file mode 100644 index 0000000..0de0e10 --- /dev/null +++ b/libs/kstd/kstd/allocator @@ -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 new file mode 100644 index 0000000..c06a8b5 --- /dev/null +++ b/libs/kstd/kstd/asm_ptr @@ -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/concepts.hpp b/libs/kstd/kstd/bits/concepts.hpp new file mode 100644 index 0000000..74c25cb --- /dev/null +++ b/libs/kstd/kstd/bits/concepts.hpp @@ -0,0 +1,15 @@ +#ifndef KSTD_BITS_CONCEPTS_HPP +#define KSTD_BITS_CONCEPTS_HPP + +#include +#include +namespace kstd::bits +{ + + template + concept container_compatible_range = + std::ranges::input_range && std::convertible_to, ValueType>; + +} + +#endif diff --git a/libs/kstd/kstd/bits/flat_map.hpp b/libs/kstd/kstd/bits/flat_map.hpp new file mode 100644 index 0000000..fe46203 --- /dev/null +++ b/libs/kstd/kstd/bits/flat_map.hpp @@ -0,0 +1,186 @@ +#ifndef KSTD_BITS_FLAT_MAP_HPP +#define KSTD_BITS_FLAT_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd::bits +{ + + template + struct flat_map_reference + { + using key_type = KeyType; + using mapped_type = MappedType; + + constexpr flat_map_reference(key_type const & key, mapped_type & mapped) + : first{key} + , second{mapped} + {} + + constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const & + { + second = other.second; + return *this; + } + + constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const & + { + second = std::move(other.second); + return *this; + } + + template + requires(std::tuple_size_v> == 2) + constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const & + { + second = std::forward(tuple).second; + return *this; + } + + template + requires(Index >= 0 && Index <= 1) + [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto) + { + if constexpr (Index == 0) + { + return (first); + } + else + { + return (second); + } + } + + key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + }; + + template + struct flat_map_pointer + { + Reference reference; + + [[nodiscard]] constexpr auto operator->() noexcept -> Reference * + { + return std::addressof(reference); + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const * + { + return std::addressof(reference); + } + }; + + template + struct flat_map_iterator + { + using iterator_category = std::random_access_iterator_tag; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using reference = flat_map_reference; + using pointer = flat_map_pointer; + + constexpr flat_map_iterator() = default; + + constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) + : m_key_iterator{key_iterator} + , m_mapped_iterator{mapped_iterator} + {} + + template + requires(std::convertible_to && + std::convertible_to) + constexpr flat_map_iterator( + flat_map_iterator const & other) noexcept + : m_key_iterator{other.m_key_iterator} + , m_mapped_iterator{other.m_mapped_iterator} + {} + + [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator + { + return m_key_iterator; + } + + [[nodiscard]] constexpr auto operator*() const noexcept -> reference + { + return {*m_key_iterator, *m_mapped_iterator}; + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> pointer + { + return { + {*m_key_iterator, *m_mapped_iterator} + }; + } + + constexpr auto operator++() noexcept -> flat_map_iterator & + { + ++m_key_iterator; + ++m_mapped_iterator; + return *this; + } + + constexpr auto operator++(int) noexcept -> flat_map_iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + constexpr auto operator--() noexcept -> flat_map_iterator & + { + --m_key_iterator; + --m_mapped_iterator; + return *this; + } + + constexpr auto operator--(int) noexcept -> flat_map_iterator + { + auto copy = *this; + --(*this); + return copy; + } + + [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator + { + return {m_key_iterator + offset, m_mapped_iterator + offset}; + } + + [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type + { + return m_key_iterator - other.m_key_iterator; + } + + [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default; + + private: + KeyIterator m_key_iterator{}; + MappedIterator m_mapped_iterator{}; + }; + +} // namespace kstd::bits + +template +struct std::tuple_size> : std::integral_constant +{ +}; + +template +struct std::tuple_element<0, kstd::bits::flat_map_reference> +{ + using type = K const &; +}; + +template +struct std::tuple_element<1, kstd::bits::flat_map_reference> +{ + using type = M &; +}; + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/arg.hpp b/libs/kstd/kstd/bits/format/arg.hpp new file mode 100644 index 0000000..e65b26f --- /dev/null +++ b/libs/kstd/kstd/bits/format/arg.hpp @@ -0,0 +1,75 @@ +#ifndef KSTD_BITS_FORMAT_ARG_HPP +#define KSTD_BITS_FORMAT_ARG_HPP + +// IWYU pragma: private, include + +#include +#include + +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + enum struct arg_type : std::uint8_t + { + none, + boolean, + character, + integer, + unsigned_integer, + string_view, + c_string, + pointer, + user_defined, + }; + } // namespace bits::format + + struct format_arg + { + bits::format::arg_type type{}; + union + { + bool boolean; + char character; + std::int64_t integer; + std::uint64_t unsigned_integer; + std::string_view string_view; + char const * c_string; + void const * pointer; + struct + { + void const * pointer; + auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void; + } user_defined; + } value{}; + }; + + namespace bits::format + { + constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t + { + if (arg.type == arg_type::unsigned_integer) + { + return static_cast(arg.value.unsigned_integer); + } + else if (arg.type == arg_type::integer) + { + if (arg.value.integer < 0) + { + error("Dynamic width cannont be negative."); + } + return static_cast(arg.value.integer); + } + error("Dynamic width argument is not an integral value."); + return 0; + } + } // namespace bits::format + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/args.hpp b/libs/kstd/kstd/bits/format/args.hpp new file mode 100644 index 0000000..e8e3114 --- /dev/null +++ b/libs/kstd/kstd/bits/format/args.hpp @@ -0,0 +1,160 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + + template + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast(value_pointer); + auto fmt = formatter>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template + constexpr auto determine_arg_type() -> arg_type + { + using decay_type = std::remove_cvref_t; + if constexpr (std::same_as) + { + return arg_type::boolean; + } + else if constexpr (std::same_as) + { + return arg_type::character; + } + else if constexpr (std::integral && std::is_signed_v) + { + return arg_type::integer; + } + else if constexpr (std::integral && std::is_unsigned_v) + { + return arg_type::unsigned_integer; + } + else if constexpr (std::same_as) + { + return arg_type::string_view; + } + else if constexpr (std::same_as, char *> || + std::same_as, char const *>) + { + return arg_type::c_string; + } + else if constexpr (std::is_pointer_v || std::same_as) + { + if constexpr (std::same_as || std::same_as) + { + return arg_type::user_defined; + } + else + { + return arg_type::pointer; + } + } + else + { + return arg_type::user_defined; + } + } + + template + constexpr auto make_single_arg(ValueType const & value) -> format_arg + { + auto result = format_arg{}; + constexpr auto type = determine_arg_type(); + result.type = type; + + if constexpr (type == arg_type::boolean) + { + result.value.boolean = value; + } + else if constexpr (type == arg_type::character) + { + result.value.character = value; + } + else if constexpr (type == arg_type::integer) + { + result.value.integer = static_cast(value); + } + else if constexpr (type == arg_type::unsigned_integer) + { + result.value.unsigned_integer = static_cast(value); + } + else if constexpr (type == arg_type::string_view) + { + result.value.string_view = value; + } + else if constexpr (type == arg_type::c_string) + { + result.value.c_string = value; + } + else if constexpr (type == arg_type::pointer) + { + if constexpr (std::same_as, std::nullptr_t>) + { + result.value.pointer = nullptr; + } + else + { + result.value.pointer = static_cast(value); + } + } + else + { + result.value.user_defined.pointer = &value; + result.value.user_defined.format = format_trampoline; + } + return result; + } + + } // namespace bits::format + + using format_args = std::span; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + using namespace bits::format; + + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{ + std::array{make_single_arg(args)...}}; + } + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/context.hpp b/libs/kstd/kstd/bits/format/context.hpp new file mode 100644 index 0000000..c166ba9 --- /dev/null +++ b/libs/kstd/kstd/bits/format/context.hpp @@ -0,0 +1,65 @@ +#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP +#define KSTD_BITS_FORMAT_CONTEXT_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto inline is_width_v = std::integral && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as; + } + + struct format_context + { + using format_args = std::span; + format_args args{}; + + format_context(bits::format::output_buffer & buffer, format_args args) + : args{args} + , m_buffer{&buffer} + {} + + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & + { + if (id >= args.size()) + { + kstd::os::panic("[kstd:format] argument index out of range!"); + } + return args[id]; + } + + constexpr auto push(std::string_view string) -> void + { + m_buffer->push(string); + } + + constexpr auto push(char character) -> void + { + m_buffer->push(character); + } + + private: + bits::format::output_buffer * m_buffer; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/error.hpp b/libs/kstd/kstd/bits/format/error.hpp new file mode 100644 index 0000000..c0cb53d --- /dev/null +++ b/libs/kstd/kstd/bits/format/error.hpp @@ -0,0 +1,24 @@ +#ifndef KSTD_BITS_FORMAT_ERROR_HPP +#define KSTD_BITS_FORMAT_ERROR_HPP + +#include + +namespace kstd::bits::format +{ + + constexpr auto error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter.hpp b/libs/kstd/kstd/bits/format/formatter.hpp new file mode 100644 index 0000000..eb28829 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter.hpp @@ -0,0 +1,92 @@ +#ifndef KSTD_BITS_FORMATTER_HPP +#define KSTD_BITS_FORMATTER_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + formatter() = delete; + formatter(formatter const &) = delete; + auto operator=(formatter const &) -> formatter & = delete; + }; + + template + struct range_formatter + { + constexpr auto set_separator(std::string_view sep) -> void + { + m_separator = sep; + } + + constexpr auto set_brackets(std::string_view opening, std::string_view closing) + { + m_prefix = opening; + m_suffix = closing; + } + + constexpr auto parse(format_parse_context & context) + { + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == 'n') + { + set_brackets("", ""); + std::advance(it, 1); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + context.advance_to(it); + it = m_inner_formatter.parse(context); + } + + if (it != end && *it != '}') + { + bits::format::error("Invalid formate specifier for range"); + } + + return it; + } + + template + auto format(Range const & range, format_context & context) + { + context.push(m_prefix); + + auto is_first = true; + std::ranges::for_each(range, [&](auto const & element) { + if (!is_first) + { + context.push(m_separator); + } + m_inner_formatter.format(element, context); + is_first = false; + }); + + context.push(m_suffix); + } + + private: + kstd::formatter m_inner_formatter{}; + std::string_view m_separator{", "}; + std::string_view m_prefix{"["}; + std::string_view m_suffix{"]"}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/bool.hpp b/libs/kstd/kstd/bits/format/formatter/bool.hpp new file mode 100644 index 0000000..cc8d190 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/bool.hpp @@ -0,0 +1,83 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 's') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for bool."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(bool value, format_context & context) const -> void + { + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, + bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/byte.hpp b/libs/kstd/kstd/bits/format/formatter/byte.hpp new file mode 100644 index 0000000..cc8aece --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/byte.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP + +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(std::byte value, format_context & context) const -> void + { + formatter::format(static_cast(value), context); + } + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/char.hpp b/libs/kstd/kstd/bits/format/formatter/char.hpp new file mode 100644 index 0000000..92489a1 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/char.hpp @@ -0,0 +1,94 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP + +#include +#include +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) + { + bits::format::error("Invalid format specifiers for 'char'"); + } + + if (it != end && *it != '}') + { + if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for char."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(char value, format_context & context) const -> void + { + if (specifiers.type == '\0' || specifiers.type == 'c') + { + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = + bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(value); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + else + { + formatter::format(static_cast(value), context); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/kstd/bits/format/formatter/cstring.hpp new file mode 100644 index 0000000..553c8ca --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/cstring.hpp @@ -0,0 +1,29 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP + +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(char const * string, format_context & context) const -> void + { + formatter::format(string ? std::string_view{string} : "(null)", context); + } + }; + + template<> + struct formatter : formatter + { + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/integral.hpp b/libs/kstd/kstd/bits/format/formatter/integral.hpp new file mode 100644 index 0000000..d17dc95 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/integral.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; + + enum struct base + { + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(T value, format_context & context) const -> void + { + auto final_width = 0uz; + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + using unsigned_T = std::make_unsigned_t; + auto absolute_value = static_cast(value); + auto is_negative = false; + + if constexpr (std::is_signed_v) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - static_cast(value); + } + } + + auto const base = [type = specifiers.type] -> auto { + switch (type) + { + case 'x': + case 'X': + case 'p': + return base::hex; + case 'b': + case 'B': + return base::bin; + case 'o': + return base::oct; + default: + return base::dec; + } + }(); + + auto buffer = std::array{}; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto current = buffer.rbegin(); + + if (absolute_value == 0) + { + *current = '0'; + std::advance(current, 1); + } + else + { + while (absolute_value != 0) + { + *current = digits[absolute_value % std::to_underlying(base)]; + std::advance(current, 1); + absolute_value /= std::to_underlying(base); + } + } + + auto content_length = static_cast(std::distance(buffer.rbegin(), current)); + auto prefix = std::array{'0', '\0'}; + auto prefix_length = 0uz; + if (specifiers.alternative_form) + { + switch (base) + { + case base::bin: + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; + prefix_length = 2; + break; + case base::oct: + prefix_length = 1; + break; + case base::hex: + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; + prefix_length = 2; + break; + default: + break; + } + } + + auto sign_character = '\0'; + if (is_negative) + { + sign_character = '-'; + } + else if (specifiers.sign == bits::format::sign_mode::plus) + { + sign_character = '+'; + } + else if (specifiers.sign == bits::format::sign_mode::space) + { + sign_character = ' '; + } + + auto const total_length = content_length + prefix_length + (sign_character != '\0'); + auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, + bits::format::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); + + if (!effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + } + + if (sign_character != '\0') + { + context.push(sign_character); + } + if (prefix_length > 0) + { + context.push(std::string_view{prefix.data(), prefix_length}); + } + + if (effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push('0'); + } + } + + context.push(std::string_view{current.base(), content_length}); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/kstd/bits/format/formatter/ordering.hpp new file mode 100644 index 0000000..7832226 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/ordering.hpp @@ -0,0 +1,111 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP + +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::strong_ordering value, format_context & context) const -> void + { + if (value == std::strong_ordering::equal) + { + return context.push(specifiers.alternative_form ? "==" : "equal"); + } + else if (value == std::strong_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::strong_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::strong_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid strong ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::weak_ordering value, format_context & context) const -> void + { + if (value == std::weak_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::weak_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::weak_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid weak ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::partial_ordering value, format_context & context) const -> void + { + if (value == std::partial_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::partial_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::partial_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + else if (value == std::partial_ordering::unordered) + { + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); + } + kstd::os::panic("[kstd:format] Invalid partial ordering value!"); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/kstd/bits/format/formatter/pointer.hpp new file mode 100644 index 0000000..15f9a5b --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/pointer.hpp @@ -0,0 +1,42 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP +#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + template + struct formatter : formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto result = formatter::parse(context); + if (!this->specifiers.type) + { + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; + } + return result; + } + + auto format(T const * pointer, format_context & context) const -> void + { + formatter::format(std::bit_cast(pointer), context); + } + }; + + template + struct formatter : formatter + { + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/range.hpp b/libs/kstd/kstd/bits/format/formatter/range.hpp new file mode 100644 index 0000000..05af06f --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/range.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP + +#include + +#include +#include +#include +#include + +namespace kstd +{ + namespace bits::format + { + template + concept iterable = requires(T const & t) { + t.begin(); + t.end(); + }; + + template + concept formattable_range = iterable && !std::same_as, std::string_view> && + !std::same_as, char *> && !std::same_as, char const *>; + } // namespace bits::format + + template + struct formatter : range_formatter> + { + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/kstd/bits/format/formatter/string_view.hpp new file mode 100644 index 0000000..7d74579 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/string_view.hpp @@ -0,0 +1,41 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP +#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP + +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + + if (it != context.end() && *it == 's') + { + ++it; + } + + if (it != context.end() && *it != '}') + { + bits::format::error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/fwd.hpp b/libs/kstd/kstd/bits/format/fwd.hpp new file mode 100644 index 0000000..6caedae --- /dev/null +++ b/libs/kstd/kstd/bits/format/fwd.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FWD_HPP +#define KSTD_BITS_FORMAT_FWD_HPP + +// IWYU pragma: private + +#include + +namespace kstd +{ + + struct format_parse_context; + struct format_context; + struct format_arg; + + template + struct formatter; + + template + struct format_arg_store; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/output_buffer.hpp b/libs/kstd/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..fd7a2b4 --- /dev/null +++ b/libs/kstd/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +// IWYU pragma: private, include + +#include + +namespace kstd::bits::format +{ + + //! An abstract interface for formatted output buffers. + //! + //! This interface is intended to be use for functions dealing with string formatting, like the print and the format + //! family. + struct output_buffer + { + virtual ~output_buffer() = default; + + //! Push a text segment into the buffer. + //! + //! @param text The text segment to push. + virtual auto push(std::string_view text) -> void = 0; + + //! Push a single character into the buffer. + //! + //! @param character The character to push into the buffer. + virtual auto push(char character) -> void = 0; + }; + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/parse_context.hpp b/libs/kstd/kstd/bits/format/parse_context.hpp new file mode 100644 index 0000000..cab8d72 --- /dev/null +++ b/libs/kstd/kstd/bits/format/parse_context.hpp @@ -0,0 +1,109 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// IWYU pragma: private, include + +#include + +#include +#include + +namespace kstd +{ + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count, + bool const * is_integral = nullptr) + : m_current{format.begin()} + , m_end{format.end()} + , m_argument_count{argument_count} + , m_is_integral{is_integral} + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + } + + constexpr auto check_dynamic_width_id(std::size_t id) -> void + { + check_arg_id(id); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + } + + constexpr auto next_dynamic_width_id() -> std::size_t + { + auto const id = next_arg_id(); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + return id; + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + bool const * m_is_integral{}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/specifiers.hpp b/libs/kstd/kstd/bits/format/specifiers.hpp new file mode 100644 index 0000000..211c95d --- /dev/null +++ b/libs/kstd/kstd/bits/format/specifiers.hpp @@ -0,0 +1,205 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include +#include + +#include +#include +#include + +namespace kstd::bits::format +{ + enum struct alignment : std::uint8_t + { + none, + left, + right, + center, + }; + + enum struct sign_mode : std::uint8_t + { + none, + plus, + minus, + space, + }; + + enum struct width_mode : std::uint8_t + { + none, + static_value, + dynamic_argument_id + }; + + struct specifiers + { + char fill{' '}; + alignment align{}; + sign_mode sign{}; + bool alternative_form{}; + bool zero_pad{}; + + width_mode mode{}; + std::size_t width_value{}; + char type{}; + }; + + struct padding + { + std::size_t left{}; + std::size_t right{}; + }; + + constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers + { + auto specs = specifiers{}; + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == '}') + { + return specs; + } + + if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) + { + specs.fill = *it; + switch (*std::next(it)) + { + case '<': + specs.align = alignment::left; + break; + case '>': + specs.align = alignment::right; + break; + case '^': + default: + specs.align = alignment::center; + break; + } + std::advance(it, 2); + } + else if (*it == '<' || *it == '>' || *it == '^') + { + switch (*it) + { + case '<': + specs.align = alignment::left; + break; + case '>': + specs.align = alignment::right; + break; + case '^': + default: + specs.align = alignment::center; + break; + } + std::advance(it, 1); + } + + if (it != end && (*it == '+' || *it == '-' || *it == ' ')) + { + switch (*it) + { + case '+': + specs.sign = sign_mode::plus; + break; + case '-': + specs.sign = sign_mode::minus; + break; + case ' ': + default: + specs.sign = sign_mode::space; + break; + } + std::advance(it, 1); + } + + if (it != end && *it == '#') + { + specs.alternative_form = true; + std::advance(it, 1); + } + + if (it != end && *it == '0') + { + specs.zero_pad = true; + std::advance(it, 1); + } + + if (it != end && *it == '{') + { + specs.mode = width_mode::dynamic_argument_id; + std::advance(it, 1); + auto argument_id = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + context.check_dynamic_width_id(argument_id); + } + else + { + argument_id = context.next_dynamic_width_id(); + } + + if (it == end || *it != '}') + { + error("Expected '}' for dynamic width."); + } + std::advance(it, 1); + specs.width_value = argument_id; + } + else if (it != end && *it >= '0' && *it <= '9') + { + specs.mode = width_mode::static_value; + while (it != end && *it >= '0' && *it <= '9') + { + specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + } + + context.advance_to(it); + return specs; + } + + constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, + alignment requested_alignment, alignment default_alignment) -> padding + { + if (target_width <= content_length) + { + return {}; + } + + auto total_padding = target_width - content_length; + auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; + + switch (effective_alignment) + { + case alignment::center: + { + auto left = total_padding / 2; + auto right = total_padding - left; + return {left, right}; + } + case alignment::left: + return {0, total_padding}; + case alignment::right: + default: + return {total_padding, 0}; + break; + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp new file mode 100644 index 0000000..e7e4088 --- /dev/null +++ b/libs/kstd/kstd/bits/format/string.hpp @@ -0,0 +1,148 @@ +#ifndef KSTD_BITS_FORMAT_STRING_HPP +#define KSTD_BITS_FORMAT_STRING_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void + { + auto found = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index && !found) + { + using decay_type = std::remove_cvref_t; + static_assert(std::is_default_constructible_v>, "Missing formatter specialization."); + auto fmt = formatter{}; + + auto it = fmt.parse(context); + context.advance_to(it); + found = true; + } + ++current_index; + }()); + + if (!found) + { + error("Argument index out of bounds."); + } + } + + template + constexpr auto validate_dynamic_width(std::size_t target_index) -> void + { + auto is_valid_integer = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index) + { + using decay_type = std::remove_cvref_t; + if constexpr (std::is_integral_v) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + error("Dynamic width argument must be an integral object."); + } + } + } // namespace bits::format + + template + struct format_string + { + template + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + using namespace bits::format; + + auto const is_width_compatible = + std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; + auto it = context.begin(); + + while (it != context.end()) + { + if (*it == '{') + { + ++it; + if (it != context.end() && *it == '{') + { + ++it; + context.advance_to(it); + continue; + } + + context.advance_to(it); + auto argument_id = 0uz; + + if (it != context.end() && *it >= '0' && *it <= '9') + { + while (it != context.end() && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + ++it; + } + context.check_arg_id(argument_id); + context.advance_to(it); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it != context.end() && *it == ':') + { + ++it; + context.advance_to(it); + } + + validate_argument(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') + { + bits::format::error("Missing closing '}' in format string."); + } + } + else if (*it == '}') + { + ++it; + if (it != context.end() && *it == '}') + { + bits::format::error("Unescaped '}' in format string."); + } + } + ++it; + context.advance_to(it); + } + } + + std::string_view str_view; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp new file mode 100644 index 0000000..994fae5 --- /dev/null +++ b/libs/kstd/kstd/bits/format/vformat.hpp @@ -0,0 +1,99 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + //! Format a string with the given arguments into the given buffer. + //! + //! External implementations of `vprint` may call this function. + //! + //! @param buffer The buffer to format into. + //! @param format The format string to use. + //! @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 + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + auto release() -> string &&; + + private: + string m_result{}; + }; + + template Output> + struct iterator_writer : output_buffer + { + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} + + [[nodiscard]] auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output).out; + } + + auto push(char character) -> void override + { + *m_output++ = character; + } + + private: + Output m_output{}; + }; + + } // namespace bits::format + + //! Format a given string with the provided arguments. + //! + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A new string containing the result of the format operation. + template + auto format(format_string...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); + return buffer.release(); + } + + //! Format a given string with the provided arguments. + //! + //! @param iterator The iterator to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return An iterator past the last element written. + template Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); + return buffer.iterator(); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.hpp b/libs/kstd/kstd/bits/observer_ptr.hpp new file mode 100644 index 0000000..2593d7a --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.hpp @@ -0,0 +1,163 @@ +#ifndef KSTD_OBSERVER_PTR_HPP +#define KSTD_OBSERVER_PTR_HPP + +// IWYU pragma: private, include + +#include + +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct observer_ptr + { + //! The type of the element being pointed to. + using element_type = ElementType; + + //! Construct an empty observer pointer. + constexpr observer_ptr() noexcept = default; + + //! Construct an empty observer pointer from a null pointer. + constexpr observer_ptr(std::nullptr_t) noexcept {} + + //! Construct an observer pointer from a raw pointer. + constexpr explicit observer_ptr(element_type * pointer) + : m_ptr{pointer} + {} + + //! Construct an observer pointer from another observer pointer. + template + requires std::convertible_to + constexpr observer_ptr(observer_ptr other) noexcept + : m_ptr{other.get()} + {} + + //! Copy construct an observer pointer. + constexpr observer_ptr(observer_ptr const & other) noexcept = default; + + //! Move construct an observer pointer. + constexpr observer_ptr(observer_ptr && other) noexcept = default; + + //! Copy assign an observer pointer. + constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default; + + //! Move assign an observer pointer. + constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default; + + //! Stop watching the the watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto release() noexcept -> element_type * + { + return std::exchange(m_ptr, nullptr); + } + + //! Reset the observer pointer. + //! + //! @param pointer The new object to watch. + constexpr auto reset(element_type * pointer = nullptr) noexcept -> void + { + m_ptr = pointer; + } + + //! Swap the observer pointer with another observer pointer. + //! + //! @param other The other observer pointer to swap with. + constexpr auto swap(observer_ptr & other) noexcept -> void + { + std::swap(m_ptr, other.m_ptr); + } + + //! Get the currently watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto get() const noexcept -> element_type * + { + return m_ptr; + } + + //! Check if the observer pointer is watching an object. + //! + //! @return True if the observer pointer is watching an object, false otherwise. + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_ptr != nullptr; + } + + //! Get the currently watched object. + //! + //! @return A reference to the currently watched object. + [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t + { + throw_on_null(); + return *m_ptr; + } + + //! Get the currently watched object. + //! + //! @return A pointer to the currently watched object. + [[nodiscard]] constexpr auto operator->() const -> element_type * + { + throw_on_null(); + return m_ptr; + } + + //! Convert the observer pointer to a raw pointer. + //! + //! @return A pointer to the currently watched object. + constexpr explicit operator element_type *() const noexcept + { + return m_ptr; + } + + //! Compare the observer pointer with another observer pointer. + //!> + //! @param other The other observer pointer to compare with. + //! @return The result of the comparison. + constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; + + private: + //! Throw an exception if the observer pointer is null. + //! + //! @throws std::runtime_error if the observer pointer is null. + constexpr auto throw_on_null() const -> void + { + if (m_ptr == nullptr) + { + os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); + } + } + + //! The raw pointer to the watched object. + ElementType * m_ptr{}; + }; + + //! Swap two observer pointers. + //! + //! @param lhs The first observer pointer to swap. + //! @param rhs The second observer pointer to swap. + template + constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void + { + lhs.swap(rhs); + } + + //! Create an observer pointer from a raw pointer. + //! + //! @param pointer The raw pointer to create an observer pointer from. + //! @return An observer pointer to the given raw pointer. + template + constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr + { + return observer_ptr{pointer}; + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp new file mode 100644 index 0000000..1ba9c63 --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.test.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/bits/print_sink.hpp b/libs/kstd/kstd/bits/print_sink.hpp new file mode 100644 index 0000000..af765e0 --- /dev/null +++ b/libs/kstd/kstd/bits/print_sink.hpp @@ -0,0 +1,17 @@ +#ifndef KSTD_BITS_PRINT_SINK_HPP +#define KSTD_BITS_PRINT_SINK_HPP + +// IWYU pragma: private, include + +namespace kstd +{ + + enum struct print_sink + { + stdout, + stderr, + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp new file mode 100644 index 0000000..8930095 --- /dev/null +++ b/libs/kstd/kstd/bits/shared_ptr.hpp @@ -0,0 +1,648 @@ +#ifndef KSTD_BITS_SHARED_PTR_HPP +#define KSTD_BITS_SHARED_PTR_HPP + +#include +#include +#include +#include + +// IWYU pragma: private, include + +namespace kstd +{ + /** + * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared + * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed + * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. + * The weak_count is needed to determine when it is safe to delete the shared_control_block itself + */ + struct shared_control_block + { + std::atomic shared_count; + std::atomic weak_count; + + explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0) + : shared_count(shared) + , weak_count(weak) + {} + }; + + template + struct shared_ptr; + + /** + * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by + * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created + * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular + * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the + * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the + * weak_ptr becomes expired and cannot be used to access the object anymore. + */ + template + struct weak_ptr + { + template + friend struct shared_ptr; + + /** + * @brief Constructs a null weak_ptr. + */ + weak_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + + template + requires(std::is_convertible_v) + weak_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. + */ + weak_ptr(weak_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. + */ + weak_ptr(weak_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. + */ + auto operator=(weak_ptr const & other) -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + if (control != nullptr) + { + ++(control->weak_count); + } + } + + return *this; + } + + /** + * @brief Move assignment operator. Move-assigns a weak_ptr from other. + */ + auto operator=(weak_ptr && other) noexcept -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + } + + return *this; + } + + /** + * @brief Destructor. Cleans up resources if necessary. + */ + ~weak_ptr() + { + cleanup(); + } + + /** + * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an + * empty shared_ptr otherwise. + */ + [[nodiscard]] auto lock() const -> shared_ptr + { + return shared_ptr(*this); + } + + private: + auto cleanup() -> void + { + if (control != nullptr) + { + if (--(control->weak_count) == 0 && control->shared_count == 0) + { + delete control; + } + } + } + + T * pointer; + shared_control_block * control; + }; + + /** + * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to + * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to + * create shared_ptr instances in a member function of the object. + * + * @tparam T The type of the managed object. + */ + template + struct enable_shared_from_this + { + template + friend struct shared_ptr; + + friend T; + + public: + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() -> shared_ptr + { + return shared_ptr(weak_this); + } + + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() const -> shared_ptr + { + return shared_ptr(weak_this); + } + + private: + enable_shared_from_this() = default; + enable_shared_from_this(enable_shared_from_this const &) = default; + auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default; + ~enable_shared_from_this() = default; + + void internal_assign_ptr(shared_ptr const & ptr) const + { + weak_this = ptr; + } + + mutable weak_ptr weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality. + }; + + /** + * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several + * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of + * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining + * shared_ptr owning the object is assigned another pointer via operator= or reset(). A + * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used + * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), + * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count + * reaches zero. + * + * @tparam T The type of the managed object. + */ + template + struct shared_ptr + { + template + friend struct shared_ptr; + + template + friend struct weak_ptr; + + /** + * @brief Construct an empty shared_ptr. + */ + shared_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + + /** + * @brief Construct an empty shared_ptr from nullptr. + */ + shared_ptr(std::nullptr_t) noexcept + : pointer(nullptr) + , control(nullptr) + {} + + /** + * @brief Constructor. + * + * @param pointer A pointer to an object to manage (default is nullptr). + */ + template + requires(std::is_convertible_v) + explicit shared_ptr(U * pointer = nullptr) + : pointer(pointer) + , control(pointer != nullptr ? new shared_control_block() : nullptr) + { + assign_enable_shared_from_this(pointer); + } + + /** + * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares + * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr. + * + * @param other The weak_ptr to construct from. + */ + template + requires(std::is_convertible_v) + explicit shared_ptr(weak_ptr const & other) + : pointer(nullptr) + , control(nullptr) + { + if (other.control != nullptr && other.control->shared_count != 0) + { + pointer = other.pointer; + control = other.control; + ++(control->shared_count); + } + } + + /** + * @brief Copy constructor. + * + * @param other The shared_ptr to copy from. + */ + shared_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->shared_count); + } + } + + /** + * @brief Converting copy constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to copy from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->shared_count); + } + } + + /** + * @brief Move constructor. + * + * @param other The shared_ptr to move from. + */ + shared_ptr(shared_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Converting move constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to move from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the + * object managed by r. If r manages no object, *this manages no object too. Equivalent to + * shared_ptr(r).swap(*this). + * + * @param other Another smart pointer to share the ownership with. + * @return Reference to this shared pointer. + */ + auto operator=(shared_ptr const & other) -> shared_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + + if (control != nullptr) + { + ++(control->shared_count); + } + } + + return *this; + } + + /** + * @brief Converting copy assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to share ownership with. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr const & other) -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + control = other.control; + + if (control != nullptr) + { + ++(control->shared_count); + } + + return *this; + } + + /** + * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of + * the previous state of r, and r is empty. Equivalent to shared_ptr(std::move(r)).swap(*this). + * + * @param other Another smart pointer to acquire the ownership from. + * @return Reference to this shared pointer. + */ + auto operator=(shared_ptr && other) noexcept -> shared_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + } + + return *this; + } + + /** + * @brief Converting move assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to acquire ownership from. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr && other) noexcept -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + + return *this; + } + + /** + * @brief Reset this shared_ptr to empty via nullptr assignment. + */ + auto operator=(std::nullptr_t) noexcept -> shared_ptr & + { + cleanup(); + pointer = nullptr; + control = nullptr; + return *this; + } + + /** + * @brief Destructor. Cleans up resources if necessary. + */ + ~shared_ptr() + { + cleanup(); + } + + /** + * @brief Replaces the managed object. + * + * @param ptr Pointer to a new object to manage (default = nullptr). + */ + void reset(T * ptr = nullptr) + { + cleanup(); + pointer = ptr; + control = ptr != nullptr ? new shared_control_block() : nullptr; + assign_enable_shared_from_this(ptr); + } + + /** + * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not + * adjusted. + * + * @param other The shared_ptr to swap with. + */ + void swap(shared_ptr & other) + { + std::swap(pointer, other.pointer); + std::swap(control, other.control); + } + + /** + * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. + * + * @return Returns the object owned by *this, equivalent to *get(). + */ + [[nodiscard]] auto operator*() const -> T & + { + return *pointer; + } + + /** + * @brief Member access operator. + * + * @return Returns a pointer to the object owned by *this, i.e. get(). + */ + [[nodiscard]] auto operator->() const -> T * + { + return pointer; + } + + /** + * @brief Returns a pointer to the managed object or nullptr if no object is owned. + * + * @return Pointer to the managed object or nullptr if no object is owned. + */ + [[nodiscard]] auto get() const -> T * + { + return pointer; + } + + /** + * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If + * there is no managed object, ​0​ is returned. + * + * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty + * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1, + * there are no other owners. + * + * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed + * object. + */ + [[nodiscard]] auto use_count() const -> std::size_t + { + if (control != nullptr) + { + return control->shared_count; + } + + return 0; + } + + /** + * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. + * + * @return true if *this owns an object, false otherwise. + */ + [[nodiscard]] explicit operator bool() const + { + return pointer != nullptr; + } + + /** + * @brief Compare shared_ptr with nullptr. + */ + [[nodiscard]] auto operator==(std::nullptr_t) const -> bool + { + return pointer == nullptr; + } + + /** + * @brief Compare nullptr with shared_ptr. + */ + [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool + { + return ptr.pointer == nullptr; + } + + private: + /** + * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this + * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the + * candidate type does not inherit from enable_shared_from_this, this function does nothing. + * + * @tparam U The candidate type to check for enable_shared_from_this inheritance. + * @param candidate The candidate object to assign the internal weak pointer for. + */ + template + auto assign_enable_shared_from_this(U * candidate) -> void + { + if constexpr (requires(U * p, shared_ptr const & sp) { p->internal_assign_ptr(sp); }) + { + if (candidate != nullptr) + { + candidate->internal_assign_ptr(*this); + } + } + } + + /** + * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object. + */ + auto cleanup() -> void + { + if (control != nullptr) + { + if (--(control->shared_count) == 0) + { + delete pointer; + pointer = nullptr; + + if (control->weak_count == 0) + { + delete control; + } + } + } + } + + T * pointer; ///< The managed object. + shared_control_block * control; ///< Shared control block. + }; + + /** + * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls + * lhs.swap(rhs). + * + * @tparam T Type of the managed object. + * @param lhs, rhs Smart pointers whose contents to swap. + */ + template + auto swap(shared_ptr & lhs, shared_ptr & rhs) -> void + { + lhs.swap(rhs); + } + + /** + * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The + * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is + * not an array type. The function is equivalent to: shared_ptr(new T(std::forward(args)...)). + * + * @tparam T Type of the managed object. + * @tparam Args Argument types for T's constructor. + * @param args List of arguments with which an instance of T will be constructed. + * @returns Shared_pointer of an instance of type T. + */ + template + auto make_shared(Args &&... args) -> shared_ptr + { + return shared_ptr(new T(std::forward(args)...)); + } + + /** + * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return true if lhs and rhs point to the same object, false otherwise. + */ + template + [[nodiscard]] auto inline operator==(shared_ptr const & lhs, shared_ptr const & rhs) -> bool + { + return lhs.get() == rhs.get(); + } + + /** + * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>. + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return The result of comparing the stored pointers of lhs and rhs using operator<=> + */ + template + [[nodiscard]] auto inline operator<=>(shared_ptr const & lhs, shared_ptr const & rhs) + { + return lhs.get() <=> rhs.get(); + } +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/unique_ptr.hpp b/libs/kstd/kstd/bits/unique_ptr.hpp new file mode 100644 index 0000000..3d803b4 --- /dev/null +++ b/libs/kstd/kstd/bits/unique_ptr.hpp @@ -0,0 +1,199 @@ +#ifndef KSTD_BITS_UNIQUE_POINTER_HPP +#define KSTD_BITS_UNIQUE_POINTER_HPP + +#include + +// IWYU pragma: private, include + +namespace kstd +{ + /** + * @brief Unique_pointer is a smart pointer that owns (is responsible for) and manages another object via a pointer + * and subsequently disposes of that object when the unique_ptr goes out of scope. + * + * @tparam T Type of the managed object. + */ + template + struct unique_ptr + { + template + friend struct unique_ptr; + + /** + * @brief Constructor. + * + * @param ptr A pointer to an object to manage (default is nullptr). + */ + explicit unique_ptr(T * ptr = nullptr) + : pointer(ptr) + { + // Nothing to do. + } + + /** + * @brief Destructor that deletes the managed object. + */ + ~unique_ptr() + { + delete pointer; + } + + /** + * @brief Deleted copy constructor to enforce unique ownership. + */ + unique_ptr(unique_ptr const &) = delete; + + template + requires(std::is_convertible_v) + unique_ptr(unique_ptr && other) noexcept + : pointer{std::exchange(other.pointer, nullptr)} + {} + + /** + * @brief Deleted copy assignment operator to enforce unique ownership. + */ + auto operator=(unique_ptr const &) -> unique_ptr & = delete; + + /** + * @brief Move constructor. + * + * @param other Unique pointer to move from. + */ + unique_ptr(unique_ptr && other) noexcept + : pointer{std::exchange(other.pointer, nullptr)} + {} + + /** + * @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()). + * + * @param other Smart pointer from which ownership will be transferred. + * @return Reference to this unique pointer. + */ + auto operator=(unique_ptr && other) noexcept -> unique_ptr & + { + if (this != &other) + { + delete pointer; + pointer = std::exchange(other.pointer, nullptr); + } + return *this; + } + + /** + * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. + * + * @return Returns the object owned by *this, equivalent to *get(). + */ + auto operator*() const -> T & + { + return *pointer; + } + + /** + * @brief Member access operator. + * + * @return Returns a pointer to the object owned by *this, i.e. get(). + */ + auto operator->() const -> T * + { + return pointer; + } + + /** + * @brief Returns a pointer to the managed object or nullptr if no object is owned. + * + * @return Pointer to the managed object or nullptr if no object is owned. + */ + [[nodiscard]] auto get() const -> T * + { + return pointer; + } + + /** + * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. + * + * @return true if *this owns an object, false otherwise. + */ + explicit operator bool() const noexcept + { + return pointer != nullptr; + } + + /** + * @brief Releases the ownership of the managed object, if any. + * get() returns nullptr after the call. + * The caller is responsible for cleaning up the object (e.g. by use of get_deleter()). + * + * @return Pointer to the managed object or nullptr if there was no managed object, i.e. the value which would be + * returned by get() before the call. + */ + auto release() -> T * + { + return std::exchange(pointer, nullptr); + } + + /** + * @brief Replaces the managed object. + * + * @note A test for self-reset, i.e. whether ptr points to an object already managed by *this, is not performed, + * except where provided as a compiler extension or as a debugging assert. Note that code such as + * p.reset(p.release()) does not involve self-reset, only code like p.reset(p.get()) does. + * + * @param ptr Pointer to a new object to manage (default = nullptr). + */ + auto reset(T * ptr = nullptr) -> void + { + delete std::exchange(pointer, ptr); + } + + /** + * @brief Swaps the managed objects and associated deleters of *this and another unique_ptr object other. + * + * @param other Another unique_ptr object to swap the managed object and the deleter with. + */ + auto swap(unique_ptr & other) -> void + { + using std::swap; + swap(pointer, other.pointer); + } + + /** + * @brief Defaulted three-way comparator operator. + */ + auto operator<=>(unique_ptr const & other) const = default; + + private: + T * pointer; ///< The managed pointer. + }; + + /** + * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls + * lhs.swap(rhs). + * + * @tparam T Type of the managed object. + * @param lhs, rhs Smart pointers whose contents to swap. + */ + template + auto swap(unique_ptr & lhs, unique_ptr & rhs) -> void + { + lhs.swap(rhs); + } + + /** + * @brief Constructs an object of type T and wraps it in a unique_ptr. Constructs a non-array type T. The + * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is + * not an array type. The function is equivalent to: unique_ptr(new T(std::forward(args)...)). + * + * @tparam T Type of the managed object. + * @tparam Args Argument types for T's constructor. + * @param args List of arguments with which an instance of T will be constructed. + * @returns Unique_pointer of an instance of type T. + */ + template + auto make_unique(Args &&... args) -> unique_ptr + { + return unique_ptr(new T(std::forward(args)...)); + } +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/cstring b/libs/kstd/kstd/cstring new file mode 100644 index 0000000..bd8b28d --- /dev/null +++ b/libs/kstd/kstd/cstring @@ -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 new file mode 100644 index 0000000..80fe9d2 --- /dev/null +++ b/libs/kstd/kstd/ext/bitfield_enum @@ -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 new file mode 100644 index 0000000..f12b1b5 --- /dev/null +++ b/libs/kstd/kstd/flat_map @@ -0,0 +1,365 @@ +#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 + { + using key_container_type = KeyContainerType; + using mapped_container_type = MappedContainerType; + using key_type = KeyType; + using mapped_type = MappedType; + using value_type = std::pair; + 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} + {} + + //! 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(); + } + + 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 new file mode 100644 index 0000000..2e5a47c --- /dev/null +++ b/libs/kstd/kstd/flat_map.test.cpp @@ -0,0 +1,314 @@ +#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)); + } + } + } +} + +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)); + } + } + } +} + +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 b/libs/kstd/kstd/format new file mode 100644 index 0000000..e04b79a --- /dev/null +++ b/libs/kstd/kstd/format @@ -0,0 +1,22 @@ +#ifndef KSTD_FORMAT_HPP +#define KSTD_FORMAT_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#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/format.test.cpp b/libs/kstd/kstd/format.test.cpp new file mode 100644 index 0000000..624779a --- /dev/null +++ b/libs/kstd/kstd/format.test.cpp @@ -0,0 +1,114 @@ +#include + +#include + +#include +#include + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with 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") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + 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") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with 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") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + 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"); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/libc/stdlib.cpp b/libs/kstd/kstd/libc/stdlib.cpp new file mode 100644 index 0000000..7ed051f --- /dev/null +++ b/libs/kstd/kstd/libc/stdlib.cpp @@ -0,0 +1,19 @@ +#include + +namespace kstd::libc +{ + + extern "C" + { + [[noreturn]] auto abort() -> void + { + kstd::os::abort(); + } + + [[noreturn, gnu::weak]] auto free(void *) -> void + { + kstd::os::panic("Tried to call free."); + } + } + +} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/kstd/libc/string.cpp b/libs/kstd/kstd/libc/string.cpp new file mode 100644 index 0000000..b7cdb82 --- /dev/null +++ b/libs/kstd/kstd/libc/string.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include +#include +#include + +namespace kstd::libc +{ + + auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + + return dest; + } + + auto memset(void * dest, int value, std::size_t size) noexcept -> void * + { + auto const byte_value = static_cast(static_cast(value)); + auto dest_span = std::span{static_cast(dest), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = byte_value; + } + + return dest; + } + + auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int + { + auto left_span = std::span{static_cast(lhs), size}; + auto right_span = std::span{static_cast(rhs), size}; + auto mismatched = std::ranges::mismatch(left_span, right_span); + + if (mismatched.in1 == left_span.end()) + { + return 0; + } + + return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); + } + + auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + if (dest < src) + { + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + } + else + { + for (std::size_t i = size; i > 0; --i) + { + dest_span[i - 1] = src_span[i - 1]; + } + } + return dest; + } + + auto strlen(char const * string) noexcept -> std::size_t + { + return std::distance(string, std::ranges::find(string, nullptr, '\0')); + } + +} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/kstd/memory b/libs/kstd/kstd/memory new file mode 100644 index 0000000..f108c6d --- /dev/null +++ b/libs/kstd/kstd/memory @@ -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 new file mode 100644 index 0000000..b2a31aa --- /dev/null +++ b/libs/kstd/kstd/mutex @@ -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/mutex.cpp b/libs/kstd/kstd/mutex.cpp new file mode 100644 index 0000000..7387657 --- /dev/null +++ b/libs/kstd/kstd/mutex.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include + +namespace kstd +{ + + mutex::mutex() = default; + + mutex::~mutex() + { + if (m_locked.test(std::memory_order_relaxed)) + { + os::panic("[KSTD] Tried to destroy a locked mutex."); + } + } + + auto mutex::lock() -> void + { + while (!try_lock()) + { + asm volatile("nop"); + } + } + + auto mutex::try_lock() -> bool + { + return !m_locked.test_and_set(std::memory_order_acquire); + } + + auto mutex::unlock() -> void + { + m_locked.clear(std::memory_order_release); + } + +} // namespace kstd diff --git a/libs/kstd/kstd/os/error.cpp b/libs/kstd/kstd/os/error.cpp new file mode 100644 index 0000000..f969cb5 --- /dev/null +++ b/libs/kstd/kstd/os/error.cpp @@ -0,0 +1,12 @@ +#include + +namespace kstd::os +{ + + [[gnu::weak, noreturn]] + auto abort() -> void + { + os::panic("Abort called."); + } + +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/os/error.hpp b/libs/kstd/kstd/os/error.hpp new file mode 100644 index 0000000..9d43fb1 --- /dev/null +++ b/libs/kstd/kstd/os/error.hpp @@ -0,0 +1,34 @@ +#ifndef KSTD_OS_ERROR_HPP +#define KSTD_OS_ERROR_HPP + +#include +#include + +namespace kstd::os +{ + /** + * @brief Handle an unrecoverable library error. + * + * The operating system kernel may choose to implement this function in order to try to perform additional cleanup + * before terminating execution. If the kernel does not implement this function, the default implementation will be + * chosen. This default implementation doest nothing. + */ + [[noreturn]] + auto abort() -> void; + + /** + * @brief Terminate execution of the operating system. + * + * The operating system must implement this function. This function must terminate the execution of the operating + * system kernel as is. It may choose to restart the kernel or to halt execution entirely. The implementation must + * guarantee that execution never return from this function. + * + * @param message A message describing the reason for termination of execution. + * @param where The source code location at which the panic was triggered. In general, no argument shall be provided + * for this parameter, thus implicitly capturing the location at which the call originates. + */ + [[noreturn]] + auto panic(std::string_view message, std::source_location where = std::source_location::current()) -> void; +} // namespace kstd::os + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/os/print.hpp b/libs/kstd/kstd/os/print.hpp new file mode 100644 index 0000000..36cb43d --- /dev/null +++ b/libs/kstd/kstd/os/print.hpp @@ -0,0 +1,14 @@ +#ifndef KSTD_OS_PRINT_HPP +#define KSTD_OS_PRINT_HPP + +#include +#include + +#include + +namespace kstd::os +{ + auto vprint(print_sink sink, std::string_view format, kstd::format_args args) -> void; +} // namespace kstd::os + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/print b/libs/kstd/kstd/print new file mode 100644 index 0000000..1033f72 --- /dev/null +++ b/libs/kstd/kstd/print @@ -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 new file mode 100644 index 0000000..78c3adb --- /dev/null +++ b/libs/kstd/kstd/ranges @@ -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 new file mode 100644 index 0000000..02e44ea --- /dev/null +++ b/libs/kstd/kstd/stack @@ -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 new file mode 100644 index 0000000..e228a04 --- /dev/null +++ b/libs/kstd/kstd/string @@ -0,0 +1,380 @@ +#ifndef KSTD_STRING_HPP +#define KSTD_STRING_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + /** + * @brief A simple string implementation that owns its data and provides basic operations. + */ + struct string + { + //! The type of the characters contained in this string. + using value_type = char; + //! The type of the underlying storage used by this string. + using storage_type = kstd::vector; + //! The type of all sizes used in and with this string. + 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 single values in this string. + using reference = value_type &; + //! The type of references to constant single values in this string. + using const_reference = value_type const &; + //! The type of pointers to single values in this string. + using pointer = value_type *; + //! The type of pointers to constant single values in this string. + using const_pointer = value_type const *; + //! The type of iterators into this string. + using iterator = pointer; + //! The type of constant iterators into this string. + using const_iterator = const_pointer; + + /** + * @brief Constructs an empty null-terminated string. + */ + string() + : m_storage{value_type{'\0'}} + {} + + /** + * @brief Constructs a string from a string view by copying the characters into owned storage. + * @param view The string view to copy the characters from. + */ + string(std::string_view view) + : string() + { + append(view); + } + + /** + * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. + * @param c_str The null-terminated C-style string to copy. + */ + string(char const * c_str) + : string() + { + if (c_str != nullptr) + { + append(std::string_view{c_str}); + } + } + + /** + * @brief Constructs a string containing a single character. + * @param c The character to copy. + */ + string(value_type c) + : string() + { + push_back(c); + } + + /** + * @brief Constructs a string by copying another string. + * @param other The string to copy. + */ + constexpr string(string const & other) + : m_storage{other.m_storage} + {} + + /** + * @brief Destructs the string. + */ + constexpr ~string() = default; + + /** + * @brief Assigns the value of another string to this string. + * @param other The string to assign from. + * @return A reference to this string. + */ + constexpr auto operator=(string const & other) -> string & = default; + + /** + * @brief Returns the number of characters in this string, not including the null terminator. + */ + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_storage.empty() ? 0 : m_storage.size() - 1; + } + + /** + * @brief Checks if this string is empty, not including the null terminator. + */ + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return size() == 0; + } + + /** + * @brief Clears the content of the string, resulting in an empty string. + * The string remains null-terminated after this operation. + */ + constexpr auto clear() -> void + { + m_storage.clear(); + m_storage.push_back(value_type{'\0'}); + } + + //! Get a pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer + { + return data(); + } + + //! Get an iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + //! Get an iterator to the end of the string + [[nodiscard]] constexpr auto end() noexcept -> iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator + { + return end(); + } + + //! Get a reference to the first character of the string + [[nodiscard]] constexpr auto front() -> reference + { + return m_storage.front(); + } + + //! Get a const reference to the first character of the string + [[nodiscard]] constexpr auto front() const -> const_reference + { + return m_storage.front(); + } + + //! Get a reference to the last character of the string + [[nodiscard]] constexpr auto back() -> reference + { + return m_storage[size() - 1]; + } + + //! Get a const reference to the last character of the string + [[nodiscard]] constexpr auto back() const -> const_reference + { + return m_storage[size() - 1]; + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + */ + constexpr auto push_back(value_type ch) -> void + { + m_storage.back() = ch; + m_storage.push_back(value_type{'\0'}); + } + + /** + * @brief Appends a string view to the end of the string by copying the characters into owned storage. + * @param view The string view to append. + * @return A reference to this string. + */ + constexpr auto append(std::string_view view) -> string & + { + if (!view.empty()) + { + std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); + } + + return *this; + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto append(string const & other) -> string & + { + return append(other.view()); + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto operator+=(string const & other) -> string & + { + return append(other); + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + * @return A reference to this string. + */ + constexpr auto operator+=(value_type ch) -> string & + { + push_back(ch); + return *this; + } + + /** + * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. + */ + [[nodiscard]] constexpr auto view() const noexcept -> std::string_view + { + return std::string_view{data(), size()}; + } + + private: + //! The underlying storage of the string, which owns the characters and ensures null-termination. + storage_type m_storage{}; + }; + + /** + * @brief Concatenates a strings and a character and returns the result as a new string. + * @param lhs The string to concatenate. + * @param rhs The string to concatenate. + * @return A new string that is the result of concatenating @p lhs and @p rhs. + */ + [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string + { + string result{lhs}; + result += rhs; + return result; + } + + /** + * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and + * concatenating them. + * @tparam N 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 + requires std::unsigned_integral + [[nodiscard]] constexpr auto inline to_string(N value) -> string + { + if (value == 0) + { + return "0"; + } + + string result; + + while (value > 0) + { + char const digit = '0' + (value % 10); + result.push_back(digit); + value /= 10; + } + + std::reverse(result.begin(), result.end()); + return result; + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool + { + return lhs.view() == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool + { + return lhs.view() == rhs; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool + { + return lhs == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool + { + return lhs.view() == std::string_view{rhs}; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool + { + return std::string_view{lhs} == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + template<> + struct formatter : formatter + { + auto format(string const & str, format_context & context) const -> void + { + formatter::format(str.view(), context); + } + }; + +} // 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 new file mode 100644 index 0000000..53d7c9a --- /dev/null +++ b/libs/kstd/kstd/string.test.cpp @@ -0,0 +1,445 @@ +#include + +#include + +#include +#include +#include + +SCENARIO("String initialization and construction", "[string]") +{ + GIVEN("Nothing") + { + WHEN("constructing an empty string") + { + auto str = kstd::string{}; + + THEN("the size is zero and therefore the string is empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string is empty") + { + REQUIRE(str.view() == std::string_view{}); + } + } + } + + GIVEN("A string view") + { + auto view = std::string_view{"Blub Blub"}; + + WHEN("constructing a string from string_view") + { + auto str = kstd::string{view}; + + THEN("the string is not empty and has the same size as the view") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == view.size()); + } + + THEN("the string contains the same characters as the view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A C-style string") + { + auto c_str = "Blub Blub"; + + WHEN("constructing a string from the C-style string") + { + auto str = kstd::string{c_str}; + + THEN("the string is not empty and has the same size as the C-style string") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == std::strlen(c_str)); + } + + THEN("the string contains the same characters as the C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } + + GIVEN("A character") + { + auto ch = 'x'; + + WHEN("constructing a string from the character") + { + auto str = kstd::string{ch}; + + THEN("the string is not empty and has size 1") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == 1); + } + + THEN("the string contains the same character as the given character") + { + REQUIRE(str.view() == std::string_view{&ch, 1}); + } + } + } + + GIVEN("Another string") + { + auto other = kstd::string{"Blub Blub"}; + + WHEN("copy constructing a new string") + { + auto str = kstd::string{other}; + + THEN("the new string contains the same characters as the original") + { + REQUIRE(str.view() == other.view()); + } + } + + auto str = kstd::string{"Blub"}; + + WHEN("copy assigning another string") + { + auto other = kstd::string{"Blub Blub"}; + str = other; + + THEN("the string contains the same characters as the assigned string") + { + REQUIRE(str.view() == other.view()); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a string view") + { + auto view = std::string_view{"Hello, world!"}; + str = view; + + THEN("the string contains the same characters as the assigned view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a C-style string") + { + auto c_str = "Hello, world!"; + str = c_str; + + THEN("the string contains the same characters as the assigned C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } +} + +SCENARIO("String concatenation", "[string]") +{ + 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.view() == "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.view() == "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 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.view() == "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()); + } + } + } + + 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.view() == "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.view() == "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.view() == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + } +} + +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.view() == "12345"); + REQUIRE(str2.view() == "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(!(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(!(str != view)); + REQUIRE(!(view != str)); + } + } +} + +SCENARIO("String clearing", "[string]") +{ + 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.view() == std::string_view{}); + } + } + } +} + +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 : str.view()) + { + 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 : str.view()) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str.view()); + } + } + + 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 : str.view()) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(result.view() == std::string_view{}); + } + } + } +} diff --git a/libs/kstd/kstd/test_support/os_panic.hpp b/libs/kstd/kstd/test_support/os_panic.hpp new file mode 100644 index 0000000..4396a9f --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_TESTS_OS_PANIC_HPP +#define KSTD_TESTS_OS_PANIC_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + struct os_panic : std::runtime_error + { + os_panic(std::string message, std::source_location location) + : std::runtime_error{message} + , location(location) + {} + + std::source_location location; + }; + +} // namespace kstd::tests + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp new file mode 100644 index 0000000..c30411a --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.test.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/test_support/test_types.hpp b/libs/kstd/kstd/test_support/test_types.hpp new file mode 100644 index 0000000..8231fd3 --- /dev/null +++ b/libs/kstd/kstd/test_support/test_types.hpp @@ -0,0 +1,313 @@ +#ifndef KSTD_TESTS_TEST_TYPES_HPP +#define KSTD_TESTS_TEST_TYPES_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + //! A type tracking copy and move operations + //! + //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. + struct special_member_tracker + { + //! A value indicating that the object was moved from. + constexpr auto static moved_from_v = -1; + + constexpr special_member_tracker() + : default_constructed_count{1} + {} + + //! Construct a new move tracker with the given value, if any. + constexpr special_member_tracker(int v = 0) + : value{v} + , value_constructed_count{1} + {} + + //! Construct a new move tracker by copying an existing one. + constexpr special_member_tracker(special_member_tracker const & other) + : value{other.value} + , copy_constructed_count{1} + {} + + //! Construct a new move tracker by moving from an existing one. + constexpr special_member_tracker(special_member_tracker && other) noexcept + : value{other.value} + , move_constructed_count{1} + { + other.value = moved_from_v; + } + + //! Copy assign a new move tracker from an existing one. + constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker & + { + if (this != &other) + { + value = other.value; + ++copy_assigned_count; + ++other.copied_from_count; + } + return *this; + } + + //! Move assign a new move tracker from an existing one. + //! + //! This function ensures that the moved-from state is marked. + constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker & + { + if (this != &other) + { + value = other.value; + ++move_assigned_count; + other.value = moved_from_v; + ++other.moved_from_count; + } + return *this; + } + + ~special_member_tracker() + { + ++destroyed_count; + } + + auto reset_counts() -> void + { + default_constructed_count = 0; + copy_constructed_count = 0; + move_constructed_count = 0; + value_constructed_count = 0; + copy_assigned_count = 0; + move_assigned_count = 0; + destroyed_count = 0; + copied_from_count = 0; + moved_from_count = 0; + } + + //! A simple value to be able to track the move-from state. + int value{}; + //! A counter to track how many times an instance of this type was default constructed. + std::size_t default_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy constructed. + std::size_t copy_constructed_count{0}; + //! A counter to track how many times an instance of this type was move constructed. + std::size_t move_constructed_count{0}; + //! A counter to track how many times an instance of this type was value constructed. + std::size_t value_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy assigned. + std::size_t copy_assigned_count{0}; + //! A counter to track how many times an instance of this type was move assigned. + std::size_t move_assigned_count{0}; + //! A counter to track how many times an instance of this type was destroyed. + std::size_t destroyed_count{0}; + //! A counter to track how many times an instance of this type was copied from another instance. + mutable std::size_t copied_from_count{0}; + //! A counter to track how many times an instance of this type was moved from another instance. + std::size_t moved_from_count{0}; + }; + + //! A type that is not default constructible. + //! + //! This type is designed to test default construction semantics of standard library containers implemented in kstd. + struct non_default_constructible + { + //! A simple placeholder value. + int value{}; + + //! Construct a new non-default-constructible object with the given value. + constexpr explicit non_default_constructible(int v) + : value{v} + {} + + //! Compare two non-default-constructible objects for equality. + [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool + { + return value == other.value; + } + }; + + //! An allocator that tracks the number of allocations. + //! + //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct tracking_allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + //! A pointer to a counter that is incremented on allocation. + //! + //! A pointer is used so that multiple allocators can share the same counter. + int * allocation_count{}; + + //! Construct a new tracking allocator referencing the given counter. + tracking_allocator(int * counter) + : allocation_count{counter} + {} + + //! Construct a new tracking allocator by copying from another tracking allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + template + tracking_allocator(tracking_allocator const & other) noexcept + : allocation_count{other.allocation_count} + {} + + //! Allocate memory for n elements of type T. + //! + //! @param n The number of elements to allocate. + //! @return A pointer to the allocated memory. + [[nodiscard]] auto allocate(std::size_t n) -> T * + { + if (allocation_count != nullptr) + { + ++(*allocation_count); + } + return static_cast(::operator new(n * sizeof(T))); + } + + //! Deallocate memory for n elements of type T. + //! + //! @param p A pointer to the memory to deallocate. + //! @param n The number of elements to deallocate. + auto deallocate(T * p, std::size_t n) noexcept -> void + { + ::operator delete(p, n * sizeof(T)); + } + + //! Compare two tracking allocators for equality. + //! + //! Two allocators are considered equal if they reference the same allocation counter. + //! + //! @param other The other tracking allocator to compare to. + //! @return True if the two tracking allocators are equal, false otherwise. + [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool + { + return allocation_count == other.allocation_count; + } + + //! Compare two tracking_allocators for inequality. + //! + //! @param other The other tracking_allocator to compare to. + //! @return True if the two tracking_allocators are not equal, false otherwise. + [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool + { + return allocation_count != other.allocation_count; + } + }; + + //! An allocator that propagates copy assignment. + //! + //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct propagating_allocator : tracking_allocator + { + //! A flag to indicate that the allocator propagates copy assignment. + using propagate_on_container_copy_assignment = std::true_type; + + //! Construct a new propagating allocator referencing the given counter. + //! + //! @param counter A pointer to a counter that is incremented on allocation. + //! @see tracking_allocator::tracking_allocator(int*) + propagating_allocator(int * counter) + : tracking_allocator{counter} + {} + + //! Construct a new propagating allocator by copying from another propagating allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + //! @see tracking_allocator::tracking_allocator(tracking_allocator const&) + template + propagating_allocator(propagating_allocator const & other) noexcept + : tracking_allocator{other.allocation_count} + {} + }; + + //! A test input iterator. + //! + //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd. + struct test_input_iterator + { + using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = int; + using reference = int const &; + using pointer = int const *; + + //! The current element pointed to by the iterator. + int const * current; + //! The number of elements in the range. + std::size_t count; + + explicit test_input_iterator() + : current{nullptr} + , count{0} + {} + + //! Construct a new test input iterator. + //! + //! @param current The current element pointed to by the iterator. + //! @param count The number of elements in the range. + explicit test_input_iterator(int const * current, std::size_t count) + : current{current} + , count{count} + {} + + //! Dereference the iterator to get the current element. + //! + //! @return The current element pointed to by the iterator. + [[nodiscard]] auto operator*() const -> int + { + return *current; + } + + //! Increment the iterator to point to the next element. + //! + //! @return A reference to the incremented iterator. + auto operator++() -> test_input_iterator & + { + ++current; + --count; + return *this; + } + + //! Increment the iterator to point to the next element. + auto operator++(int) -> void + { + ++*this; + } + + //! Compare two test input iterators for equality. + //! + //! @param other The other test input iterator to compare to. + //! @return True if the two test input iterators are equal, false otherwise. + [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool + { + if (current == nullptr && other.current == nullptr) + { + return true; + } + + if (current == nullptr || other.current == nullptr) + { + return count == other.count; + } + + return current == other.current && count == other.count; + } + }; + +} // namespace kstd::tests + +#endif diff --git a/libs/kstd/kstd/units b/libs/kstd/kstd/units new file mode 100644 index 0000000..df5eb37 --- /dev/null +++ b/libs/kstd/kstd/units @@ -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 new file mode 100644 index 0000000..c714957 --- /dev/null +++ b/libs/kstd/kstd/vector @@ -0,0 +1,1154 @@ +#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."); + } + + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + + //! 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; + } + + auto new_data = allocate_n(m_size); + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, old_size); + clear_and_deallocate(); + std::exchange(m_data, new_data); + m_capacity = old_size; + m_size = old_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 + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(value); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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, value); + 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; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = value; + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value; + } + + ++m_size; + return begin() + prefix_size; + } + + //! 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 + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(std::move(value)); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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::move(value)); + 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; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = std::move(value); + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value); + } + + ++m_size; + return begin() + prefix_size; + } + + //! 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(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + emplace_back(std::forward(args)...); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + 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; + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value_type{std::forward(args)...}; + } + + ++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 + { + if (m_capacity == m_size) + { + 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 + m_size, value); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), value); + } + ++m_size; + } + + //! Append a given element to this vector via move construction. + constexpr auto push_back(value_type && value) -> void + { + if (m_capacity == m_size) + { + 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 + m_size, std::move(value)); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); + } + ++m_size; + } + + //! Append a given element to this vector via direct construction. + template + constexpr auto emplace_back(Args &&... args) -> reference + { + if (m_capacity == m_size) + { + 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 + m_size, std::forward(args)...); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + 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; + } + } + + //! 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++)); + } + } + + //! 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 new file mode 100644 index 0000000..8bf8f79 --- /dev/null +++ b/libs/kstd/kstd/vector.test.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); + } + } + } +} diff --git a/libs/kstd/kstd/vformat.cpp b/libs/kstd/kstd/vformat.cpp new file mode 100644 index 0000000..b7c5121 --- /dev/null +++ b/libs/kstd/kstd/vformat.cpp @@ -0,0 +1,209 @@ +#include +#include + +#include +#include +#include +#include + +namespace kstd::bits::format +{ + + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void + { + auto context = kstd::format_context{buffer, args}; + auto parse_context = kstd::format_parse_context{format, args.size()}; + + auto it = parse_context.begin(); + auto end = parse_context.end(); + + while (it != end) + { + if (*it != '{' && *it != '}') + { + auto start = it; + while (it != end && *it != '{' && *it != '}') + { + std::advance(it, 1); + } + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); + continue; + } + + if (*it == '{') + { + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } + + parse_context.advance_to(it); + auto index = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + } + + parse_context.advance_to(it); + + if (index < args.size()) + { + auto const & arg = args[index]; + switch (arg.type) + { + case kstd::bits::format::arg_type::boolean: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.boolean, context); + break; + } + case kstd::bits::format::arg_type::character: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.character, context); + break; + } + case kstd::bits::format::arg_type::integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.integer, context); + break; + } + case kstd::bits::format::arg_type::unsigned_integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.unsigned_integer, context); + break; + } + case kstd::bits::format::arg_type::string_view: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.string_view, context); + break; + } + case kstd::bits::format::arg_type::c_string: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.c_string, context); + break; + } + case kstd::bits::format::arg_type::pointer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.pointer, context); + break; + } + case kstd::bits::format::arg_type::user_defined: + { + if (arg.value.user_defined.format) + { + arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + break; + } + default: + { + context.push("{fmt-err: unknown-type}"); + break; + } + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } + } + else if (*it == '}') + { + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } + } + } + } + + auto string_writer::push(std::string_view text) -> void + { + m_result.append(text); + } + + auto string_writer::push(char character) -> void + { + m_result.push_back(character); + } + + auto string_writer::release() -> string && + { + return std::move(m_result); + } + +} // namespace kstd::bits::format \ No newline at end of file diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/src/libc/stdlib.cpp deleted file mode 100644 index a18fed0..0000000 --- a/libs/kstd/src/libc/stdlib.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -namespace kstd::libc -{ - - extern "C" - { - [[noreturn]] auto abort() -> void - { - kstd::os::abort(); - } - - [[noreturn, gnu::weak]] auto free(void *) -> void - { - kstd::os::panic("Tried to call free."); - } - } - -} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp deleted file mode 100644 index c9fada1..0000000 --- a/libs/kstd/src/libc/string.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -namespace kstd::libc -{ - - auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void * - { - auto dest_span = std::span{static_cast(dest), size}; - auto src_span = std::span{static_cast(src), size}; - - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = src_span[i]; - } - - return dest; - } - - auto memset(void * dest, int value, std::size_t size) noexcept -> void * - { - auto const byte_value = static_cast(static_cast(value)); - auto dest_span = std::span{static_cast(dest), size}; - - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = byte_value; - } - - return dest; - } - - auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int - { - auto left_span = std::span{static_cast(lhs), size}; - auto right_span = std::span{static_cast(rhs), size}; - auto mismatched = std::ranges::mismatch(left_span, right_span); - - if (mismatched.in1 == left_span.end()) - { - return 0; - } - - return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); - } - - auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void * - { - auto dest_span = std::span{static_cast(dest), size}; - auto src_span = std::span{static_cast(src), size}; - if (dest < src) - { - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = src_span[i]; - } - } - else - { - for (std::size_t i = size; i > 0; --i) - { - dest_span[i - 1] = src_span[i - 1]; - } - } - return dest; - } - - auto strlen(char const * string) noexcept -> std::size_t - { - return std::distance(string, std::ranges::find(string, nullptr, '\0')); - } - -} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/src/mutex.cpp b/libs/kstd/src/mutex.cpp deleted file mode 100644 index 7387657..0000000 --- a/libs/kstd/src/mutex.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -#include - -#include - -namespace kstd -{ - - mutex::mutex() = default; - - mutex::~mutex() - { - if (m_locked.test(std::memory_order_relaxed)) - { - os::panic("[KSTD] Tried to destroy a locked mutex."); - } - } - - auto mutex::lock() -> void - { - while (!try_lock()) - { - asm volatile("nop"); - } - } - - auto mutex::try_lock() -> bool - { - return !m_locked.test_and_set(std::memory_order_acquire); - } - - auto mutex::unlock() -> void - { - m_locked.clear(std::memory_order_release); - } - -} // namespace kstd diff --git a/libs/kstd/src/os/error.cpp b/libs/kstd/src/os/error.cpp deleted file mode 100644 index f969cb5..0000000 --- a/libs/kstd/src/os/error.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -namespace kstd::os -{ - - [[gnu::weak, noreturn]] - auto abort() -> void - { - os::panic("Abort called."); - } - -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp deleted file mode 100644 index b7c5121..0000000 --- a/libs/kstd/src/vformat.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -namespace kstd::bits::format -{ - - auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void - { - auto context = kstd::format_context{buffer, args}; - auto parse_context = kstd::format_parse_context{format, args.size()}; - - auto it = parse_context.begin(); - auto end = parse_context.end(); - - while (it != end) - { - if (*it != '{' && *it != '}') - { - auto start = it; - while (it != end && *it != '{' && *it != '}') - { - std::advance(it, 1); - } - parse_context.advance_to(it); - context.push(std::string_view(start, it - start)); - continue; - } - - if (*it == '{') - { - std::advance(it, 1); - if (it != end && *it == '{') - { - context.push('{'); - std::advance(it, 1); - parse_context.advance_to(it); - continue; - } - - parse_context.advance_to(it); - auto index = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - index = index * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - parse_context.check_arg_id(index); - } - else - { - index = parse_context.next_arg_id(); - } - - if (it != end && *it == ':') - { - std::advance(it, 1); - } - - parse_context.advance_to(it); - - if (index < args.size()) - { - auto const & arg = args[index]; - switch (arg.type) - { - case kstd::bits::format::arg_type::boolean: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.boolean, context); - break; - } - case kstd::bits::format::arg_type::character: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.character, context); - break; - } - case kstd::bits::format::arg_type::integer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.integer, context); - break; - } - case kstd::bits::format::arg_type::unsigned_integer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.unsigned_integer, context); - break; - } - case kstd::bits::format::arg_type::string_view: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.string_view, context); - break; - } - case kstd::bits::format::arg_type::c_string: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.c_string, context); - break; - } - case kstd::bits::format::arg_type::pointer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.pointer, context); - break; - } - case kstd::bits::format::arg_type::user_defined: - { - if (arg.value.user_defined.format) - { - arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); - } - else - { - context.push("{?}"); - } - break; - } - default: - { - context.push("{fmt-err: unknown-type}"); - break; - } - } - } - else - { - context.push("{fmt-err: bound}"); - } - - it = parse_context.begin(); - - if (it != end && *it == '}') - { - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unconsumed}"); - while (it != end && *it != '}') - { - std::advance(it, 1); - } - - if (it != end) - { - std::advance(it, 1); - parse_context.advance_to(it); - } - } - } - else if (*it == '}') - { - std::advance(it, 1); - if (it != end && *it == '}') - { - context.push('}'); - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unescaped}"); - parse_context.advance_to(it); - } - } - } - } - - auto string_writer::push(std::string_view text) -> void - { - m_result.append(text); - } - - auto string_writer::push(char character) -> void - { - m_result.push_back(character); - } - - auto string_writer::release() -> string && - { - return std::move(m_result); - } - -} // namespace kstd::bits::format \ No newline at end of file diff --git a/libs/kstd/tests/include/kstd/tests/os_panic.hpp b/libs/kstd/tests/include/kstd/tests/os_panic.hpp deleted file mode 100644 index 4396a9f..0000000 --- a/libs/kstd/tests/include/kstd/tests/os_panic.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_TESTS_OS_PANIC_HPP -#define KSTD_TESTS_OS_PANIC_HPP - -#include -#include -#include - -namespace kstd::tests -{ - - struct os_panic : std::runtime_error - { - os_panic(std::string message, std::source_location location) - : std::runtime_error{message} - , location(location) - {} - - std::source_location location; - }; - -} // namespace kstd::tests - -#endif \ No newline at end of file diff --git a/libs/kstd/tests/include/kstd/tests/test_types.hpp b/libs/kstd/tests/include/kstd/tests/test_types.hpp deleted file mode 100644 index 8231fd3..0000000 --- a/libs/kstd/tests/include/kstd/tests/test_types.hpp +++ /dev/null @@ -1,313 +0,0 @@ -#ifndef KSTD_TESTS_TEST_TYPES_HPP -#define KSTD_TESTS_TEST_TYPES_HPP - -#include -#include -#include - -namespace kstd::tests -{ - - //! A type tracking copy and move operations - //! - //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. - struct special_member_tracker - { - //! A value indicating that the object was moved from. - constexpr auto static moved_from_v = -1; - - constexpr special_member_tracker() - : default_constructed_count{1} - {} - - //! Construct a new move tracker with the given value, if any. - constexpr special_member_tracker(int v = 0) - : value{v} - , value_constructed_count{1} - {} - - //! Construct a new move tracker by copying an existing one. - constexpr special_member_tracker(special_member_tracker const & other) - : value{other.value} - , copy_constructed_count{1} - {} - - //! Construct a new move tracker by moving from an existing one. - constexpr special_member_tracker(special_member_tracker && other) noexcept - : value{other.value} - , move_constructed_count{1} - { - other.value = moved_from_v; - } - - //! Copy assign a new move tracker from an existing one. - constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker & - { - if (this != &other) - { - value = other.value; - ++copy_assigned_count; - ++other.copied_from_count; - } - return *this; - } - - //! Move assign a new move tracker from an existing one. - //! - //! This function ensures that the moved-from state is marked. - constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker & - { - if (this != &other) - { - value = other.value; - ++move_assigned_count; - other.value = moved_from_v; - ++other.moved_from_count; - } - return *this; - } - - ~special_member_tracker() - { - ++destroyed_count; - } - - auto reset_counts() -> void - { - default_constructed_count = 0; - copy_constructed_count = 0; - move_constructed_count = 0; - value_constructed_count = 0; - copy_assigned_count = 0; - move_assigned_count = 0; - destroyed_count = 0; - copied_from_count = 0; - moved_from_count = 0; - } - - //! A simple value to be able to track the move-from state. - int value{}; - //! A counter to track how many times an instance of this type was default constructed. - std::size_t default_constructed_count{0}; - //! A counter to track how many times an instance of this type was copy constructed. - std::size_t copy_constructed_count{0}; - //! A counter to track how many times an instance of this type was move constructed. - std::size_t move_constructed_count{0}; - //! A counter to track how many times an instance of this type was value constructed. - std::size_t value_constructed_count{0}; - //! A counter to track how many times an instance of this type was copy assigned. - std::size_t copy_assigned_count{0}; - //! A counter to track how many times an instance of this type was move assigned. - std::size_t move_assigned_count{0}; - //! A counter to track how many times an instance of this type was destroyed. - std::size_t destroyed_count{0}; - //! A counter to track how many times an instance of this type was copied from another instance. - mutable std::size_t copied_from_count{0}; - //! A counter to track how many times an instance of this type was moved from another instance. - std::size_t moved_from_count{0}; - }; - - //! A type that is not default constructible. - //! - //! This type is designed to test default construction semantics of standard library containers implemented in kstd. - struct non_default_constructible - { - //! A simple placeholder value. - int value{}; - - //! Construct a new non-default-constructible object with the given value. - constexpr explicit non_default_constructible(int v) - : value{v} - {} - - //! Compare two non-default-constructible objects for equality. - [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool - { - return value == other.value; - } - }; - - //! An allocator that tracks the number of allocations. - //! - //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd. - //! - //! @tparam T The type of the elements to be allocated. - template - struct tracking_allocator - { - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - //! A pointer to a counter that is incremented on allocation. - //! - //! A pointer is used so that multiple allocators can share the same counter. - int * allocation_count{}; - - //! Construct a new tracking allocator referencing the given counter. - tracking_allocator(int * counter) - : allocation_count{counter} - {} - - //! Construct a new tracking allocator by copying from another tracking allocator. - //! - //! This constructor is templated to allow for conversion from allocators of different types. - //! - //! @tparam U The type of the elements to be allocated by the other allocator. - template - tracking_allocator(tracking_allocator const & other) noexcept - : allocation_count{other.allocation_count} - {} - - //! Allocate memory for n elements of type T. - //! - //! @param n The number of elements to allocate. - //! @return A pointer to the allocated memory. - [[nodiscard]] auto allocate(std::size_t n) -> T * - { - if (allocation_count != nullptr) - { - ++(*allocation_count); - } - return static_cast(::operator new(n * sizeof(T))); - } - - //! Deallocate memory for n elements of type T. - //! - //! @param p A pointer to the memory to deallocate. - //! @param n The number of elements to deallocate. - auto deallocate(T * p, std::size_t n) noexcept -> void - { - ::operator delete(p, n * sizeof(T)); - } - - //! Compare two tracking allocators for equality. - //! - //! Two allocators are considered equal if they reference the same allocation counter. - //! - //! @param other The other tracking allocator to compare to. - //! @return True if the two tracking allocators are equal, false otherwise. - [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool - { - return allocation_count == other.allocation_count; - } - - //! Compare two tracking_allocators for inequality. - //! - //! @param other The other tracking_allocator to compare to. - //! @return True if the two tracking_allocators are not equal, false otherwise. - [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool - { - return allocation_count != other.allocation_count; - } - }; - - //! An allocator that propagates copy assignment. - //! - //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd. - //! - //! @tparam T The type of the elements to be allocated. - template - struct propagating_allocator : tracking_allocator - { - //! A flag to indicate that the allocator propagates copy assignment. - using propagate_on_container_copy_assignment = std::true_type; - - //! Construct a new propagating allocator referencing the given counter. - //! - //! @param counter A pointer to a counter that is incremented on allocation. - //! @see tracking_allocator::tracking_allocator(int*) - propagating_allocator(int * counter) - : tracking_allocator{counter} - {} - - //! Construct a new propagating allocator by copying from another propagating allocator. - //! - //! This constructor is templated to allow for conversion from allocators of different types. - //! - //! @tparam U The type of the elements to be allocated by the other allocator. - //! @see tracking_allocator::tracking_allocator(tracking_allocator const&) - template - propagating_allocator(propagating_allocator const & other) noexcept - : tracking_allocator{other.allocation_count} - {} - }; - - //! A test input iterator. - //! - //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd. - struct test_input_iterator - { - using iterator_concept = std::input_iterator_tag; - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = int; - using reference = int const &; - using pointer = int const *; - - //! The current element pointed to by the iterator. - int const * current; - //! The number of elements in the range. - std::size_t count; - - explicit test_input_iterator() - : current{nullptr} - , count{0} - {} - - //! Construct a new test input iterator. - //! - //! @param current The current element pointed to by the iterator. - //! @param count The number of elements in the range. - explicit test_input_iterator(int const * current, std::size_t count) - : current{current} - , count{count} - {} - - //! Dereference the iterator to get the current element. - //! - //! @return The current element pointed to by the iterator. - [[nodiscard]] auto operator*() const -> int - { - return *current; - } - - //! Increment the iterator to point to the next element. - //! - //! @return A reference to the incremented iterator. - auto operator++() -> test_input_iterator & - { - ++current; - --count; - return *this; - } - - //! Increment the iterator to point to the next element. - auto operator++(int) -> void - { - ++*this; - } - - //! Compare two test input iterators for equality. - //! - //! @param other The other test input iterator to compare to. - //! @return True if the two test input iterators are equal, false otherwise. - [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool - { - if (current == nullptr && other.current == nullptr) - { - return true; - } - - if (current == nullptr || other.current == nullptr) - { - return count == other.count; - } - - return current == other.current && count == other.count; - } - }; - -} // namespace kstd::tests - -#endif diff --git a/libs/kstd/tests/src/flat_map.cpp b/libs/kstd/tests/src/flat_map.cpp deleted file mode 100644 index 2b793d9..0000000 --- a/libs/kstd/tests/src/flat_map.cpp +++ /dev/null @@ -1,314 +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)); - } - } - } -} - -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)); - } - } - } -} - -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/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp deleted file mode 100644 index 624779a..0000000 --- a/libs/kstd/tests/src/format.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include - -#include - -#include -#include - -SCENARIO("Formatting to a new string", "[format]") -{ - GIVEN("a format string without any placeholders") - { - auto const & fmt = "This is a test"; - - WHEN("calling format with 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") - { - auto const & fmt = "Here are some placeholders: {} {} {}"; - - 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") - { - auto const & fmt = "This is a test"; - - WHEN("calling format with 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") - { - auto const & fmt = "Here are some placeholders: {} {} {}"; - - 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"); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp deleted file mode 100644 index 0c94892..0000000 --- a/libs/kstd/tests/src/observer_ptr.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/tests/src/os_panic.cpp b/libs/kstd/tests/src/os_panic.cpp deleted file mode 100644 index 0759763..0000000 --- a/libs/kstd/tests/src/os_panic.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/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp deleted file mode 100644 index 53d7c9a..0000000 --- a/libs/kstd/tests/src/string.cpp +++ /dev/null @@ -1,445 +0,0 @@ -#include - -#include - -#include -#include -#include - -SCENARIO("String initialization and construction", "[string]") -{ - GIVEN("Nothing") - { - WHEN("constructing an empty string") - { - auto str = kstd::string{}; - - THEN("the size is zero and therefore the string is empty") - { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - - THEN("the string is empty") - { - REQUIRE(str.view() == std::string_view{}); - } - } - } - - GIVEN("A string view") - { - auto view = std::string_view{"Blub Blub"}; - - WHEN("constructing a string from string_view") - { - auto str = kstd::string{view}; - - THEN("the string is not empty and has the same size as the view") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == view.size()); - } - - THEN("the string contains the same characters as the view") - { - REQUIRE(str.view() == view); - } - } - } - - GIVEN("A C-style string") - { - auto c_str = "Blub Blub"; - - WHEN("constructing a string from the C-style string") - { - auto str = kstd::string{c_str}; - - THEN("the string is not empty and has the same size as the C-style string") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == std::strlen(c_str)); - } - - THEN("the string contains the same characters as the C-style string") - { - REQUIRE(str.view() == c_str); - } - } - } - - GIVEN("A character") - { - auto ch = 'x'; - - WHEN("constructing a string from the character") - { - auto str = kstd::string{ch}; - - THEN("the string is not empty and has size 1") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == 1); - } - - THEN("the string contains the same character as the given character") - { - REQUIRE(str.view() == std::string_view{&ch, 1}); - } - } - } - - GIVEN("Another string") - { - auto other = kstd::string{"Blub Blub"}; - - WHEN("copy constructing a new string") - { - auto str = kstd::string{other}; - - THEN("the new string contains the same characters as the original") - { - REQUIRE(str.view() == other.view()); - } - } - - auto str = kstd::string{"Blub"}; - - WHEN("copy assigning another string") - { - auto other = kstd::string{"Blub Blub"}; - str = other; - - THEN("the string contains the same characters as the assigned string") - { - REQUIRE(str.view() == other.view()); - } - } - } - - GIVEN("A string") - { - auto str = kstd::string{"Hello"}; - - WHEN("copy assigning a string view") - { - auto view = std::string_view{"Hello, world!"}; - str = view; - - THEN("the string contains the same characters as the assigned view") - { - REQUIRE(str.view() == view); - } - } - } - - GIVEN("A string") - { - auto str = kstd::string{"Hello"}; - - WHEN("copy assigning a C-style string") - { - auto c_str = "Hello, world!"; - str = c_str; - - THEN("the string contains the same characters as the assigned C-style string") - { - REQUIRE(str.view() == c_str); - } - } - } -} - -SCENARIO("String concatenation", "[string]") -{ - 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.view() == "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.view() == "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 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.view() == "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()); - } - } - } - - 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.view() == "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.view() == "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.view() == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - } -} - -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.view() == "12345"); - REQUIRE(str2.view() == "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(!(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(!(str != view)); - REQUIRE(!(view != str)); - } - } -} - -SCENARIO("String clearing", "[string]") -{ - 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.view() == std::string_view{}); - } - } - } -} - -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 : str.view()) - { - 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 : str.view()) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str.view()); - } - } - - 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 : str.view()) - { - result.push_back(ch); - } - - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(result.view() == std::string_view{}); - } - } - } -} diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp deleted file mode 100644 index 4fb3559..0000000 --- a/libs/kstd/tests/src/vector.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); - } - } - } -} -- cgit v1.2.3 From 1246e00478fb5ab2a357de17066fd8738395d9f1 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 4 May 2026 08:20:42 +0200 Subject: debug: split gdb modules --- libs/kstd/gdb/__init__.py | 20 +++++++++++++++++ libs/kstd/gdb/smart_pointers.py | 50 +++++++++++++++++++++++++++++++++++++++++ libs/kstd/gdb/std_types.py | 15 +++++++++++++ libs/kstd/gdb/string.py | 27 ++++++++++++++++++++++ libs/kstd/gdb/vector.py | 22 ++++++++++++++++++ 5 files changed, 134 insertions(+) create mode 100644 libs/kstd/gdb/__init__.py create mode 100644 libs/kstd/gdb/smart_pointers.py create mode 100644 libs/kstd/gdb/std_types.py create mode 100644 libs/kstd/gdb/string.py create mode 100644 libs/kstd/gdb/vector.py (limited to 'libs') diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py new file mode 100644 index 0000000..fc5e8fb --- /dev/null +++ b/libs/kstd/gdb/__init__.py @@ -0,0 +1,20 @@ +import gdb.printing + +from .vector import KstdVectorPrinter +from .string import KstdStringPrinter +from .std_types import StdBytePrinter +from .smart_pointers import KstdUniquePtrPrinter, KstdSharedPtrPrinter + + +def build_pretty_printers(): + pp = gdb.printing.RegexpCollectionPrettyPrinter("kstd") + pp.add_printer("vector", "^kstd::vector<.*>$", KstdVectorPrinter) + pp.add_printer("string", "^kstd::string$", KstdStringPrinter) + pp.add_printer("std_byte", "^std::byte$", StdBytePrinter) + pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter) + pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter) + return pp + + +def register_printers(objfile): + gdb.printing.register_pretty_printer(objfile, build_pretty_printers(), replace=True) diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py new file mode 100644 index 0000000..6e4a4f9 --- /dev/null +++ b/libs/kstd/gdb/smart_pointers.py @@ -0,0 +1,50 @@ +import gdb + + +class KstdUniquePtrPrinter: + + def __init__(self, val): + self.val = val + self.type = val.type.template_argument(0) + + def to_string(self): + pointer = self.val["pointer"] + if int(pointer) == 0: + return f"kstd::unique_ptr<{self.type}> (empty)" + return f"kstd::unique_ptr<{self.type}>" + + def children(self): + pointer = self.val["pointer"] + if int(pointer) != 0: + yield ("get()", pointer) + yield ("*get()", pointer.dereference()) + + +class KstdSharedPtrPrinter: + + def __init__(self, val): + self.val = val + self.type = val.type.template_argument(0) + + def to_string(self): + pointer = self.val["pointer"] + control_block = self.val["control"] + + if int(pointer) == 0 or int(control_block) == 0: + return f"kstd::shared_ptr<{self.type}> (empty)" + + strong_refs = int(control_block["shared_count"]["_M_i"]) + weak_refs = int(control_block["weak_count"]["_M_i"]) + + return f"kstd::shared_ptr<{self.type}> (use_count={strong_refs}, weak_count={weak_refs})" + + def children(self): + pointer = self.val["pointer"] + control_block = self.val["control"] + + if int(pointer) != 0: + yield ("get()", pointer) + yield ("*get()", pointer.dereference()) + + if int(control_block) != 0: + yield ("[control_block]", control_block.dereference()) diff --git a/libs/kstd/gdb/std_types.py b/libs/kstd/gdb/std_types.py new file mode 100644 index 0000000..78d094c --- /dev/null +++ b/libs/kstd/gdb/std_types.py @@ -0,0 +1,15 @@ +import gdb + + +class StdBytePrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + try: + uint8_type = gdb.lookup_type("unsigned char") + numeric_value = int(self.val.cast(uint8_type)) + return f"{numeric_value:#04x}" + except gdb.error: + return f"" diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py new file mode 100644 index 0000000..8230b21 --- /dev/null +++ b/libs/kstd/gdb/string.py @@ -0,0 +1,27 @@ +import gdb + + +class KstdStringPrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + storage = self.val["m_storage"] + storage_size = int(storage["m_size"]) + + if storage_size <= 0: + return '""' + + data_pointer = storage["m_data"] + string_length = storage_size - 1 + + try: + if hasattr(data_pointer, "lazy_string"): + return data_pointer.lazy_string(encoding="utf-8", length=string_length) + return data_pointer.string(encoding="utf-8", length=string_length) + except gdb.error: + return "" + + def display_hint(self): + return "string" diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py new file mode 100644 index 0000000..597ffdc --- /dev/null +++ b/libs/kstd/gdb/vector.py @@ -0,0 +1,22 @@ +import gdb + + +class KstdVectorPrinter: + + def __init__(self, val): + self.val = val + self.type = val.type.template_argument(0) + + def to_string(self): + size = int(self.val["m_size"]) + capacity = int(self.val["m_capacity"]) + return f"kstd::vector of length {size}, capacity {capacity}" + + def children(self): + size = int(self.val["m_size"]) + data_pointer = self.val["m_data"] + for i in range(size): + yield (f"[{i}]", (data_pointer + i).dereference()) + + def display_hint(self): + return "array" -- cgit v1.2.3 From 78e42a1b6e0a857865be1e60f82871ac13c91bb1 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 4 May 2026 12:01:01 +0200 Subject: debug: improve pretty printer implementations --- libs/kstd/gdb/smart_pointers.py | 43 +++++++++++++++++++++-------------------- libs/kstd/gdb/std_types.py | 8 +++----- libs/kstd/gdb/string.py | 4 ++-- libs/kstd/gdb/vector.py | 19 +++++++++--------- 4 files changed, 37 insertions(+), 37 deletions(-) (limited to 'libs') diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py index 6e4a4f9..3e5da02 100644 --- a/libs/kstd/gdb/smart_pointers.py +++ b/libs/kstd/gdb/smart_pointers.py @@ -1,50 +1,51 @@ import gdb +from teachos import TeachOSBasePrinter -class KstdUniquePtrPrinter: - +class KstdUniquePtrPrinter(TeachOSBasePrinter): def __init__(self, val): - self.val = val - self.type = val.type.template_argument(0) + super().__init__(val) + self.__type = val.type.template_argument(0) def to_string(self): - pointer = self.val["pointer"] + pointer = self.value["pointer"] if int(pointer) == 0: - return f"kstd::unique_ptr<{self.type}> (empty)" - return f"kstd::unique_ptr<{self.type}>" + return f"kstd::unique_ptr<{self.__type}> (empty)" + return f"kstd::unique_ptr<{self.__type}>" def children(self): - pointer = self.val["pointer"] + pointer = self.value["pointer"] if int(pointer) != 0: - yield ("get()", pointer) - yield ("*get()", pointer.dereference()) + yield ("[object]", pointer.dereference()) + yield from super().children() -class KstdSharedPtrPrinter: +class KstdSharedPtrPrinter(TeachOSBasePrinter): def __init__(self, val): - self.val = val - self.type = val.type.template_argument(0) + super().__init__(val) + self.__type = val.type.template_argument(0) def to_string(self): - pointer = self.val["pointer"] - control_block = self.val["control"] + pointer = self.value["pointer"] + control_block = self.value["control"] if int(pointer) == 0 or int(control_block) == 0: - return f"kstd::shared_ptr<{self.type}> (empty)" + return f"kstd::shared_ptr<{self.__type}> (empty)" strong_refs = int(control_block["shared_count"]["_M_i"]) weak_refs = int(control_block["weak_count"]["_M_i"]) - return f"kstd::shared_ptr<{self.type}> (use_count={strong_refs}, weak_count={weak_refs})" + return f"kstd::shared_ptr<{self.__type}> (use_count={strong_refs}, weak_count={weak_refs})" def children(self): - pointer = self.val["pointer"] - control_block = self.val["control"] + pointer = self.value["pointer"] + control_block = self.value["control"] if int(pointer) != 0: - yield ("get()", pointer) - yield ("*get()", pointer.dereference()) + yield ("[object]", pointer.dereference()) if int(control_block) != 0: yield ("[control_block]", control_block.dereference()) + + yield from super().children() diff --git a/libs/kstd/gdb/std_types.py b/libs/kstd/gdb/std_types.py index 78d094c..deb5c58 100644 --- a/libs/kstd/gdb/std_types.py +++ b/libs/kstd/gdb/std_types.py @@ -1,15 +1,13 @@ import gdb +from teachos import TeachOSBasePrinter -class StdBytePrinter: - - def __init__(self, val): - self.val = val +class StdBytePrinter(TeachOSBasePrinter): def to_string(self): try: uint8_type = gdb.lookup_type("unsigned char") - numeric_value = int(self.val.cast(uint8_type)) + numeric_value = int(self.value.cast(uint8_type)) return f"{numeric_value:#04x}" except gdb.error: return f"" diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py index 8230b21..2688061 100644 --- a/libs/kstd/gdb/string.py +++ b/libs/kstd/gdb/string.py @@ -4,10 +4,10 @@ import gdb class KstdStringPrinter: def __init__(self, val): - self.val = val + self.__val = val def to_string(self): - storage = self.val["m_storage"] + storage = self.__val["m_storage"] storage_size = int(storage["m_size"]) if storage_size <= 0: diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py index 597ffdc..4340ef4 100644 --- a/libs/kstd/gdb/vector.py +++ b/libs/kstd/gdb/vector.py @@ -1,20 +1,21 @@ import gdb +from teachos import TeachOSBasePrinter -class KstdVectorPrinter: - +class KstdVectorPrinter(TeachOSBasePrinter): def __init__(self, val): - self.val = val - self.type = val.type.template_argument(0) + super().__init__(val) + self.__type = val.type.template_argument(0) def to_string(self): - size = int(self.val["m_size"]) - capacity = int(self.val["m_capacity"]) - return f"kstd::vector of length {size}, capacity {capacity}" + size = int(self.value["m_size"]) + capacity = int(self.value["m_capacity"]) + return f"kstd::vector<{self.__type}> (size={size}, capacity={capacity})" def children(self): - size = int(self.val["m_size"]) - data_pointer = self.val["m_data"] + yield from super().children() + size = int(self.value["m_size"]) + data_pointer = self.value["m_data"] for i in range(size): yield (f"[{i}]", (data_pointer + i).dereference()) -- cgit v1.2.3 From 40fbefab704695b905e3de3e80668447cc64b20e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 15:36:19 +0200 Subject: refactoring and extend tests --- libs/kstd/kstd/string.test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index 53d7c9a..9755676 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -37,7 +37,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has the same size as the view") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == view.size()); } @@ -58,7 +58,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has the same size as the C-style string") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == std::strlen(c_str)); } @@ -79,7 +79,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has size 1") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == 1); } @@ -294,7 +294,7 @@ SCENARIO("String conversion and comparison", "[string]") THEN("the strings are not unequal") { - REQUIRE(!(str1 != str2)); + REQUIRE_FALSE(str1 != str2); } } @@ -311,8 +311,8 @@ SCENARIO("String conversion and comparison", "[string]") THEN("the string and the string view are not unequal") { - REQUIRE(!(str != view)); - REQUIRE(!(view != str)); + REQUIRE_FALSE(str != view); + REQUIRE_FALSE(view != str); } } } -- cgit v1.2.3 From 0ea43527332b7e5f1cfec6007506aa54e8f628cb Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 7 May 2026 09:12:05 +0000 Subject: debug: enable libstdc++ helpers --- libs/kstd/gdb/__init__.py | 2 -- libs/kstd/gdb/std_types.py | 13 ------------- 2 files changed, 15 deletions(-) delete mode 100644 libs/kstd/gdb/std_types.py (limited to 'libs') diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py index fc5e8fb..2d61539 100644 --- a/libs/kstd/gdb/__init__.py +++ b/libs/kstd/gdb/__init__.py @@ -2,7 +2,6 @@ import gdb.printing from .vector import KstdVectorPrinter from .string import KstdStringPrinter -from .std_types import StdBytePrinter from .smart_pointers import KstdUniquePtrPrinter, KstdSharedPtrPrinter @@ -10,7 +9,6 @@ def build_pretty_printers(): pp = gdb.printing.RegexpCollectionPrettyPrinter("kstd") pp.add_printer("vector", "^kstd::vector<.*>$", KstdVectorPrinter) pp.add_printer("string", "^kstd::string$", KstdStringPrinter) - pp.add_printer("std_byte", "^std::byte$", StdBytePrinter) pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter) pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter) return pp diff --git a/libs/kstd/gdb/std_types.py b/libs/kstd/gdb/std_types.py deleted file mode 100644 index deb5c58..0000000 --- a/libs/kstd/gdb/std_types.py +++ /dev/null @@ -1,13 +0,0 @@ -import gdb -from teachos import TeachOSBasePrinter - - -class StdBytePrinter(TeachOSBasePrinter): - - def to_string(self): - try: - uint8_type = gdb.lookup_type("unsigned char") - numeric_value = int(self.value.cast(uint8_type)) - return f"{numeric_value:#04x}" - except gdb.error: - return f"" -- cgit v1.2.3 From 35829497bdc0e00aa8f32b1855079fa5e2e0b084 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 7 May 2026 11:41:31 +0200 Subject: debug: align kstd printers with std ones --- libs/kstd/gdb/smart_pointers.py | 13 ++++++------- libs/kstd/gdb/vector.py | 10 ++++------ 2 files changed, 10 insertions(+), 13 deletions(-) (limited to 'libs') diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py index 3e5da02..0c14c77 100644 --- a/libs/kstd/gdb/smart_pointers.py +++ b/libs/kstd/gdb/smart_pointers.py @@ -16,7 +16,7 @@ class KstdUniquePtrPrinter(TeachOSBasePrinter): def children(self): pointer = self.value["pointer"] if int(pointer) != 0: - yield ("[object]", pointer.dereference()) + yield ("get()", pointer.dereference()) yield from super().children() @@ -31,21 +31,20 @@ class KstdSharedPtrPrinter(TeachOSBasePrinter): control_block = self.value["control"] if int(pointer) == 0 or int(control_block) == 0: - return f"kstd::shared_ptr<{self.__type}> (empty)" + return f"shared_ptr<{self.__type}> (empty)" strong_refs = int(control_block["shared_count"]["_M_i"]) weak_refs = int(control_block["weak_count"]["_M_i"]) - return f"kstd::shared_ptr<{self.__type}> (use_count={strong_refs}, weak_count={weak_refs})" + return ( + f"shared_ptr<{self.__type}> use count {strong_refs}, weak count {weak_refs}" + ) def children(self): pointer = self.value["pointer"] control_block = self.value["control"] if int(pointer) != 0: - yield ("[object]", pointer.dereference()) - - if int(control_block) != 0: - yield ("[control_block]", control_block.dereference()) + yield ("get()", pointer.dereference()) yield from super().children() diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py index 4340ef4..b3604de 100644 --- a/libs/kstd/gdb/vector.py +++ b/libs/kstd/gdb/vector.py @@ -3,17 +3,12 @@ from teachos import TeachOSBasePrinter class KstdVectorPrinter(TeachOSBasePrinter): - def __init__(self, val): - super().__init__(val) - self.__type = val.type.template_argument(0) - def to_string(self): size = int(self.value["m_size"]) capacity = int(self.value["m_capacity"]) - return f"kstd::vector<{self.__type}> (size={size}, capacity={capacity})" + return f"vector of length {size}, capacity {capacity}" def children(self): - yield from super().children() size = int(self.value["m_size"]) data_pointer = self.value["m_data"] for i in range(size): @@ -21,3 +16,6 @@ class KstdVectorPrinter(TeachOSBasePrinter): def display_hint(self): return "array" + + def num_children(self): + return int(self.value["m_size"]) -- cgit v1.2.3 From 6ac1537d07dffa3482bbccf710a77a7316191c2e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 7 May 2026 12:09:27 +0200 Subject: debug: use gdb.ValuePrinter base class --- libs/kstd/gdb/smart_pointers.py | 27 ++++++++++++++------------- libs/kstd/gdb/string.py | 2 +- libs/kstd/gdb/units.py | 0 libs/kstd/gdb/vector.py | 25 ++++++++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 libs/kstd/gdb/units.py (limited to 'libs') diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py index 0c14c77..b94d466 100644 --- a/libs/kstd/gdb/smart_pointers.py +++ b/libs/kstd/gdb/smart_pointers.py @@ -1,34 +1,34 @@ import gdb -from teachos import TeachOSBasePrinter -class KstdUniquePtrPrinter(TeachOSBasePrinter): +class KstdUniquePtrPrinter(gdb.ValuePrinter): def __init__(self, val): - super().__init__(val) + self.__val = val self.__type = val.type.template_argument(0) def to_string(self): - pointer = self.value["pointer"] + pointer = self.__val["pointer"] if int(pointer) == 0: return f"kstd::unique_ptr<{self.__type}> (empty)" return f"kstd::unique_ptr<{self.__type}>" def children(self): - pointer = self.value["pointer"] + pointer = self.__val["pointer"] if int(pointer) != 0: yield ("get()", pointer.dereference()) - yield from super().children() + def display_hint(self): + return None -class KstdSharedPtrPrinter(TeachOSBasePrinter): +class KstdSharedPtrPrinter(gdb.ValuePrinter): def __init__(self, val): - super().__init__(val) + self.__val = val self.__type = val.type.template_argument(0) def to_string(self): - pointer = self.value["pointer"] - control_block = self.value["control"] + pointer = self.__val["pointer"] + control_block = self.__val["control"] if int(pointer) == 0 or int(control_block) == 0: return f"shared_ptr<{self.__type}> (empty)" @@ -41,10 +41,11 @@ class KstdSharedPtrPrinter(TeachOSBasePrinter): ) def children(self): - pointer = self.value["pointer"] - control_block = self.value["control"] + pointer = self.__val["pointer"] + control_block = self.__val["control"] if int(pointer) != 0: yield ("get()", pointer.dereference()) - yield from super().children() + def display_hint(self): + return None diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py index 2688061..73c22d6 100644 --- a/libs/kstd/gdb/string.py +++ b/libs/kstd/gdb/string.py @@ -1,7 +1,7 @@ import gdb -class KstdStringPrinter: +class KstdStringPrinter(gdb.ValuePrinter): def __init__(self, val): self.__val = val diff --git a/libs/kstd/gdb/units.py b/libs/kstd/gdb/units.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py index b3604de..69f8ca2 100644 --- a/libs/kstd/gdb/vector.py +++ b/libs/kstd/gdb/vector.py @@ -1,21 +1,28 @@ import gdb -from teachos import TeachOSBasePrinter -class KstdVectorPrinter(TeachOSBasePrinter): +class KstdVectorPrinter(gdb.ValuePrinter): + def __init__(self, val): + self.__val = val + self.__size = int(val["m_size"]) + self.__capacity = int(val["m_capacity"]) + def to_string(self): - size = int(self.value["m_size"]) - capacity = int(self.value["m_capacity"]) - return f"vector of length {size}, capacity {capacity}" + return f"vector of length {self.__size}, capacity {self.__capacity}" def children(self): - size = int(self.value["m_size"]) - data_pointer = self.value["m_data"] - for i in range(size): + data_pointer = self.__val["m_data"] + for i in range(self.__size): yield (f"[{i}]", (data_pointer + i).dereference()) + def child(self, n): + if n < self.__size: + return (f"[{n}]", (self.__val["m_data"] + n).dereference()) + else: + raise gdb.MemoryError("Index out of range") + def display_hint(self): return "array" def num_children(self): - return int(self.value["m_size"]) + return self.__size -- cgit v1.2.3 From fb09cd6633b26ef2cfb4f21b8cd852611cfe59d8 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 7 May 2026 12:17:01 +0200 Subject: debug: add support for kstd::observer_ptr --- libs/kstd/gdb/__init__.py | 7 ++++++- libs/kstd/gdb/smart_pointers.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) (limited to 'libs') diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py index 2d61539..c5d1e53 100644 --- a/libs/kstd/gdb/__init__.py +++ b/libs/kstd/gdb/__init__.py @@ -2,7 +2,11 @@ import gdb.printing from .vector import KstdVectorPrinter from .string import KstdStringPrinter -from .smart_pointers import KstdUniquePtrPrinter, KstdSharedPtrPrinter +from .smart_pointers import ( + KstdUniquePtrPrinter, + KstdSharedPtrPrinter, + KstdObserverPtrPrinter, +) def build_pretty_printers(): @@ -11,6 +15,7 @@ def build_pretty_printers(): pp.add_printer("string", "^kstd::string$", KstdStringPrinter) pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter) pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter) + pp.add_printer("observer_ptr", "^kstd::observer_ptr<.*>$", KstdObserverPtrPrinter) return pp diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py index b94d466..f6e8a45 100644 --- a/libs/kstd/gdb/smart_pointers.py +++ b/libs/kstd/gdb/smart_pointers.py @@ -49,3 +49,20 @@ class KstdSharedPtrPrinter(gdb.ValuePrinter): def display_hint(self): return None + + +class KstdObserverPtrPrinter(gdb.ValuePrinter): + def __init__(self, val): + self.__val = val + self.__type = val.type.template_argument(0) + self.__pointer = val["m_ptr"] + + def to_string(self): + return f"{(self.__pointer)}" + + def children(self): + if int(self.__pointer) != 0: + yield ("get()", self.__pointer.dereference()) + + def display_hint(self): + return None -- cgit v1.2.3 From 2cb7a2575e8eb46df36dae108fae661b91801540 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 10 May 2026 12:18:01 +0200 Subject: debug: add pretty printer for boot modules registry --- libs/kstd/gdb/vector.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) (limited to 'libs') diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py index 69f8ca2..f11e064 100644 --- a/libs/kstd/gdb/vector.py +++ b/libs/kstd/gdb/vector.py @@ -2,24 +2,37 @@ import gdb class KstdVectorPrinter(gdb.ValuePrinter): - def __init__(self, val): + class Iterator: + def __init__(self, begin: gdb.Value, end: gdb.Value): + self._item = begin + self._end = end + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self._count + self._count = count + 1 + + if self._item == self._end: + raise StopIteration + + element = self._item.dereference() + self._item = self._item + 1 + return (f"[{count}]", element) + + def __init__(self, val: gdb.Value): self.__val = val self.__size = int(val["m_size"]) self.__capacity = int(val["m_capacity"]) + self.__data = val["m_data"] def to_string(self): return f"vector of length {self.__size}, capacity {self.__capacity}" def children(self): - data_pointer = self.__val["m_data"] - for i in range(self.__size): - yield (f"[{i}]", (data_pointer + i).dereference()) - - def child(self, n): - if n < self.__size: - return (f"[{n}]", (self.__val["m_data"] + n).dereference()) - else: - raise gdb.MemoryError("Index out of range") + return self.Iterator(self.__data, self.__data + self.__size) def display_hint(self): return "array" -- cgit v1.2.3 From 0ffee4e5dbc20dd7f1f7991d1f8dab698fc9b7a0 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 10 May 2026 17:12:27 +0200 Subject: kstd/vector: reduce code duplication --- libs/kstd/kstd/vector | 257 ++++++++++++++++++-------------------------------- 1 file changed, 91 insertions(+), 166 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/vector b/libs/kstd/kstd/vector index c714957..736b854 100644 --- a/libs/kstd/kstd/vector +++ b/libs/kstd/kstd/vector @@ -551,13 +551,7 @@ namespace kstd kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible."); } - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; + reallocate_exactly(new_capacity); } //! Resize this vector to contain @p new_size elements. @@ -613,13 +607,7 @@ namespace kstd return; } - auto new_data = allocate_n(m_size); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, old_size); - clear_and_deallocate(); - std::exchange(m_data, new_data); - m_capacity = old_size; - m_size = old_size; + reallocate_exactly(m_size); } //! Clear the contents of this vector. @@ -636,47 +624,7 @@ namespace kstd //! @return An iterator to the inserted element. constexpr auto insert(const_iterator position, value_type const & value) -> iterator { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(value); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - 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, value); - 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; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = value; - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value; - } - - ++m_size; - return begin() + prefix_size; + return do_insert(position, value); } //! Insert an element at a given position. @@ -686,47 +634,7 @@ namespace kstd //! @return An iterator to the inserted element. constexpr auto insert(const_iterator position, value_type && value) -> iterator { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(std::move(value)); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - 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::move(value)); - 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; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = std::move(value); - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value); - } - - ++m_size; - return begin() + prefix_size; + return do_insert(position, std::move(value)); } //! Insert the element of a given range into the vector at a given position. @@ -801,39 +709,24 @@ namespace kstd template constexpr auto emplace(const_iterator position, Args &&... args) -> iterator { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) + auto prefix_size = std::ranges::distance(cbegin(), position); + if (position == cend()) { emplace_back(std::forward(args)...); - return begin() + prefix_size; } - - if (m_capacity == m_size) + else if (m_capacity == m_size) { - 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; + reallocate_and_insert(begin() + prefix_size, std::forward(args)...); } else { + auto to_insert = value_type{std::forward(args)...}; auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value_type{std::forward(args)...}; + shift_back(insert_position); + *insert_position = std::move(to_insert); + ++m_size; } - ++m_size; return begin() + prefix_size; } @@ -884,47 +777,13 @@ namespace kstd //! Append a given element to this vector via copy construction. constexpr auto push_back(value_type const & value) -> void { - if (m_capacity == m_size) - { - 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 + m_size, value); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), value); - } - ++m_size; + emplace_back(value); } //! Append a given element to this vector via move construction. constexpr auto push_back(value_type && value) -> void { - if (m_capacity == m_size) - { - 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 + m_size, std::move(value)); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); - } - ++m_size; + emplace_back(std::move(value)); } //! Append a given element to this vector via direct construction. @@ -933,22 +792,13 @@ namespace kstd { if (m_capacity == m_size) { - 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 + m_size, std::forward(args)...); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; + reallocate_and_insert(end(), std::forward(args)...); } else { std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); + ++m_size; } - ++m_size; return this->back(); } @@ -1055,6 +905,38 @@ namespace kstd } } + //! 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. @@ -1105,6 +987,49 @@ namespace kstd } } + //! 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{}; -- cgit v1.2.3 From a50d6cfcea67b11f6689ec825afc2e2b33252714 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 18 May 2026 15:21:39 +0200 Subject: ci: enable jUnit style test reports --- libs/acpi/CMakeLists.txt | 2 +- libs/kstd/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 2c4d76d..135ce6a 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -132,5 +132,5 @@ if(BUILD_TESTING) EXCLUDE_FROM_ALL NO ) - catch_discover_tests("acpi_tests") + catch_discover_tests("acpi_tests" ${CATCH_TEST_ARGS}) endif() diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 6902891..1cc75b7 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -114,5 +114,5 @@ if(BUILD_TESTING) enable_coverage("kstd_tests") endif() - catch_discover_tests("kstd::tests") + catch_discover_tests("kstd::tests" ${CATCH_TEST_ARGS}) endif() \ No newline at end of file -- cgit v1.2.3 From 033ecf6714089d2ce331152f5e120567f8d546cf Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 19 May 2026 12:48:08 +0200 Subject: build: clean up dependencies --- libs/acpi/CMakeLists.txt | 26 ++++---------------------- libs/elf/CMakeLists.txt | 21 --------------------- libs/kstd/CMakeLists.txt | 24 +++--------------------- libs/multiboot2/CMakeLists.txt | 21 --------------------- 4 files changed, 7 insertions(+), 85 deletions(-) (limited to 'libs') diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 135ce6a..1d03bb5 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -8,27 +8,6 @@ project("acpi" include("CTest") -#[============================================================================[ -# External Dependencies -#]============================================================================] - -include("FetchContent") - -if (BUILD_TESTING) - FetchContent_Declare( - "Catch2" - URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" - URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" - EXCLUDE_FROM_ALL - FIND_PACKAGE_ARGS - ) - - FetchContent_MakeAvailable("Catch2") - - find_package("Catch2") - include("Catch") -endif() - #[============================================================================[ # Library #]============================================================================] @@ -75,6 +54,9 @@ set_target_properties("acpi" PROPERTIES #]============================================================================] if(BUILD_TESTING) + find_package("Catch2") + include("Catch") + find_program(IASL_EXE NAMES "iasl" REQUIRED) set(TEST_TABLES @@ -132,5 +114,5 @@ if(BUILD_TESTING) EXCLUDE_FROM_ALL NO ) - catch_discover_tests("acpi_tests" ${CATCH_TEST_ARGS}) + catch_discover_tests("acpi::tests" ${CATCH_TEST_ARGS}) endif() diff --git a/libs/elf/CMakeLists.txt b/libs/elf/CMakeLists.txt index 22ca200..1841132 100644 --- a/libs/elf/CMakeLists.txt +++ b/libs/elf/CMakeLists.txt @@ -8,27 +8,6 @@ project("elf" include("CTest") -#[============================================================================[ -# External Dependencies -#]============================================================================] - -include("FetchContent") - -if (BUILD_TESTING) - FetchContent_Declare( - "Catch2" - URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" - URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" - EXCLUDE_FROM_ALL - FIND_PACKAGE_ARGS - ) - - FetchContent_MakeAvailable("Catch2") - - find_package("Catch2") - include("Catch") -endif() - #[============================================================================[ # Library #]============================================================================] diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 1cc75b7..0f64761 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -8,27 +8,6 @@ project("kstd" include("CTest") -#[============================================================================[ -# External Dependencies -#]============================================================================] - -include("FetchContent") - -if (BUILD_TESTING) - FetchContent_Declare( - "Catch2" - URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" - URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" - EXCLUDE_FROM_ALL - FIND_PACKAGE_ARGS - ) - - FetchContent_MakeAvailable("Catch2") - - find_package("Catch2") - include("Catch") -endif() - #[============================================================================[ # Library #]============================================================================] @@ -88,6 +67,9 @@ endif() #]============================================================================] if(BUILD_TESTING) + find_package("Catch2") + include("Catch") + add_executable("kstd_tests") add_executable("kstd::tests" ALIAS "kstd_tests") diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt index da5fb53..5ab56db 100644 --- a/libs/multiboot2/CMakeLists.txt +++ b/libs/multiboot2/CMakeLists.txt @@ -8,27 +8,6 @@ project("multiboot2" include("CTest") -#[============================================================================[ -# External Dependencies -#]============================================================================] - -include("FetchContent") - -if (BUILD_TESTING) - FetchContent_Declare( - "Catch2" - URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz" - URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c" - EXCLUDE_FROM_ALL - FIND_PACKAGE_ARGS - ) - - FetchContent_MakeAvailable("Catch2") - - find_package("Catch2") - include("Catch") -endif() - #[============================================================================[ # Library #]============================================================================] -- cgit v1.2.3 From 2063d3e165a1b92a46c73badf56927228ed4d5e8 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 25 May 2026 10:15:21 +0200 Subject: Refactor ssize_t --- libs/kstd/kstd/unikstd.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 libs/kstd/kstd/unikstd.h (limited to 'libs') diff --git a/libs/kstd/kstd/unikstd.h b/libs/kstd/kstd/unikstd.h new file mode 100644 index 0000000..aa60be6 --- /dev/null +++ b/libs/kstd/kstd/unikstd.h @@ -0,0 +1,12 @@ +#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 6c8b068c15e28e91117f84cb8d5789f5fe6fcbd0 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 1 Jun 2026 20:31:57 +0200 Subject: kstd/string: simplify comparisons --- libs/kstd/kstd/string | 105 ++++++++++++++++++----------------------- libs/kstd/kstd/string.test.cpp | 44 ++++++++--------- 2 files changed, 69 insertions(+), 80 deletions(-) (limited to 'libs') diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string index e228a04..9343b42 100644 --- a/libs/kstd/kstd/string +++ b/libs/kstd/kstd/string @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -52,7 +53,7 @@ namespace kstd * @brief Constructs a string from a string view by copying the characters into owned storage. * @param view The string view to copy the characters from. */ - string(std::string_view view) + explicit string(std::string_view view) : string() { append(view); @@ -101,6 +102,26 @@ namespace kstd */ constexpr auto operator=(string const & other) -> string & = default; + constexpr auto operator=(std::string_view const & other) -> string & + { + clear(); + append(other); + return *this; + } + + constexpr auto operator=(char const * other) -> string & + { + clear(); + append(std::string_view{other}); + return *this; + } + + //! Create a string view from this string. + constexpr operator std::string_view() const noexcept + { + return {data(), size()}; + } + /** * @brief Returns the number of characters in this string, not including the null terminator. */ @@ -224,6 +245,7 @@ namespace kstd { if (!view.empty()) { + m_storage.reserve(size() + view.size() + 1); std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); } @@ -237,7 +259,7 @@ namespace kstd */ constexpr auto append(string const & other) -> string & { - return append(other.view()); + return append(static_cast(other)); } /** @@ -261,12 +283,29 @@ namespace kstd return *this; } - /** - * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. - */ - [[nodiscard]] constexpr auto view() const noexcept -> std::string_view + //! Compare this string lexicographically to another string. + //! + //! @param other The string to compare to this one. + [[nodiscard]] constexpr auto operator<=>(string const & other) const noexcept -> std::strong_ordering = default; + + [[nodiscard]] constexpr auto operator==(string const & other) const noexcept -> bool = default; + + //! Compare this string lexicographically to a C-style string. + //! + //! @param other The C-style string to compare to this one. + //! @return The result of the comparison. + [[nodiscard]] constexpr auto operator<=>(char const * other) const noexcept + { + return static_cast(*this) <=> other; + } + + //! Check if this string compares equal to a C-style string. + //! + //! @param other The C-style string to compare to this one. + //! @return @p true iff. this string compares equal to @p other, @p false otherwise. + [[nodiscard]] constexpr auto operator==(char const * other) const noexcept -> bool { - return std::string_view{data(), size()}; + return static_cast(*this) == other; } private: @@ -316,62 +355,12 @@ namespace kstd return result; } - [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool - { - return lhs.view() == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool - { - return lhs.view() == rhs; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool - { - return lhs == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool - { - return lhs.view() == std::string_view{rhs}; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool - { - return std::string_view{lhs} == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - template<> struct formatter : formatter { auto format(string const & str, format_context & context) const -> void { - formatter::format(str.view(), context); + formatter::format(static_cast(str), context); } }; diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index 9755676..b81cd3a 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -22,7 +22,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is empty") { - REQUIRE(str.view() == std::string_view{}); + REQUIRE(str == std::string_view{}); } } } @@ -43,7 +43,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same characters as the view") { - REQUIRE(str.view() == view); + REQUIRE(str == view); } } } @@ -64,7 +64,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same characters as the C-style string") { - REQUIRE(str.view() == c_str); + REQUIRE(str == c_str); } } } @@ -85,7 +85,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same character as the given character") { - REQUIRE(str.view() == std::string_view{&ch, 1}); + REQUIRE(str == std::string_view{&ch, 1}); } } } @@ -100,7 +100,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the new string contains the same characters as the original") { - REQUIRE(str.view() == other.view()); + REQUIRE(static_cast(str) == static_cast(other)); } } @@ -113,7 +113,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same characters as the assigned string") { - REQUIRE(str.view() == other.view()); + REQUIRE(static_cast(str) == static_cast(other)); } } } @@ -129,7 +129,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same characters as the assigned view") { - REQUIRE(str.view() == view); + REQUIRE(str == view); } } } @@ -145,7 +145,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string contains the same characters as the assigned C-style string") { - REQUIRE(str.view() == c_str); + REQUIRE(str == c_str); } } } @@ -164,7 +164,7 @@ SCENARIO("String concatenation", "[string]") THEN("the first string contains the characters of both strings concatenated") { - REQUIRE(str1.view() == "Blub Blub"); + REQUIRE(str1 == "Blub Blub"); } THEN("the size of the first string is the sum of the sizes of both strings") @@ -179,7 +179,7 @@ SCENARIO("String concatenation", "[string]") THEN("the first string contains the characters of both strings concatenated") { - REQUIRE(str1.view() == "Blub Blub"); + REQUIRE(str1 == "Blub Blub"); } THEN("the size of the first string is the sum of the sizes of both strings") @@ -194,7 +194,7 @@ SCENARIO("String concatenation", "[string]") THEN("the new string contains the characters of both strings concatenated") { - REQUIRE(str3.view() == "Blub Blub"); + REQUIRE(str3 == "Blub Blub"); } THEN("the size of the new string is the sum of the sizes of both strings") @@ -215,7 +215,7 @@ SCENARIO("String concatenation", "[string]") THEN("the string contains the characters of both the original string and the appended view concatenated") { - REQUIRE(str.view() == "Blub Blub"); + REQUIRE(str == "Blub Blub"); } THEN("the size of the string is the sum of the sizes of the original string and the appended view") @@ -236,7 +236,7 @@ SCENARIO("String concatenation", "[string]") THEN("the string contains the original characters followed by the appended character") { - REQUIRE(str.view() == "Blub!"); + REQUIRE(str == "Blub!"); } THEN("the size of the string is one more than the original size") @@ -251,7 +251,7 @@ SCENARIO("String concatenation", "[string]") THEN("the string contains the original characters followed by the appended character") { - REQUIRE(str.view() == "Blub!"); + REQUIRE(str == "Blub!"); } THEN("the size of the string is one more than the original size") @@ -276,8 +276,8 @@ SCENARIO("String conversion and comparison", "[string]") THEN("the string contains the decimal representation of the unsigned integer") { - REQUIRE(str1.view() == "12345"); - REQUIRE(str2.view() == "0"); + REQUIRE(str1 == "12345"); + REQUIRE(str2 == "0"); } } } @@ -335,7 +335,7 @@ SCENARIO("String clearing", "[string]") THEN("the string contains no characters") { - REQUIRE(str.view() == std::string_view{}); + REQUIRE(str == std::string_view{}); } } } @@ -351,7 +351,7 @@ SCENARIO("String iteration", "[string]") { kstd::string result; - for (auto ch : str.view()) + for (auto ch : static_cast(str)) { result.push_back(ch); } @@ -396,14 +396,14 @@ SCENARIO("String iteration", "[string]") { kstd::string result; - for (auto ch : str.view()) + 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.view()); + REQUIRE(result == static_cast(str)); } } @@ -429,7 +429,7 @@ SCENARIO("String iteration", "[string]") { kstd::string result; - for (auto ch : str.view()) + for (auto ch : static_cast(str)) { result.push_back(ch); } @@ -438,7 +438,7 @@ SCENARIO("String iteration", "[string]") { REQUIRE(result.empty()); REQUIRE(result.size() == 0); - REQUIRE(result.view() == std::string_view{}); + REQUIRE(static_cast(result) == std::string_view{}); } } } -- cgit v1.2.3 From 772861fc5fae1c126fcc63a8809b0a9c729bd152 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 2 Jun 2026 13:43:49 +0200 Subject: kernel/vfs: add type registry tests --- libs/kstd/kstd/flat_map | 16 ++++++++++++++++ libs/kstd/kstd/flat_map.test.cpp | 10 ++++++++++ 2 files changed, 26 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map index f12b1b5..943e9fc 100644 --- a/libs/kstd/kstd/flat_map +++ b/libs/kstd/kstd/flat_map @@ -356,6 +356,22 @@ namespace kstd 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; diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp index 2e5a47c..6c57173 100644 --- a/libs/kstd/kstd/flat_map.test.cpp +++ b/libs/kstd/kstd/flat_map.test.cpp @@ -20,6 +20,16 @@ SCENARIO("Flat Map initialization and construction", "[flat_map]") { 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()); + } } } } -- cgit v1.2.3 From 46d3f8978e9f4235064daf5f19de5bf3054e7c24 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 2 Jun 2026 16:38:30 +0200 Subject: acpi: fix two lint issues --- libs/acpi/acpi/common/basic_table.hpp | 5 +++++ libs/acpi/acpi/common/vla_table.hpp | 5 +++++ 2 files changed, 10 insertions(+) (limited to 'libs') diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp index 33f23d5..f5b5b27 100644 --- a/libs/acpi/acpi/common/basic_table.hpp +++ b/libs/acpi/acpi/common/basic_table.hpp @@ -28,6 +28,11 @@ namespace acpi { return signature() == table_signature_v && validate_checksum(); } + + private: + friend TableType; + + constexpr basic_table() noexcept = default; }; } // namespace acpi diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp index a65a28e..d3f33a7 100644 --- a/libs/acpi/acpi/common/vla_table.hpp +++ b/libs/acpi/acpi/common/vla_table.hpp @@ -109,6 +109,11 @@ namespace acpi { return end(); } + + private: + friend TableType; + + constexpr vla_table() noexcept = default; }; } // namespace acpi -- cgit v1.2.3 From 0a772bdb97ea7eccc7bdf079e7bc09e5f27c1718 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 3 Jun 2026 05:51:34 +0200 Subject: kstd: implement flat_map copy assignment --- libs/kstd/kstd/flat_map | 25 +++++++++++++++++++++++++ libs/kstd/kstd/flat_map.test.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) (limited to 'libs') diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map index 943e9fc..e51eb91 100644 --- a/libs/kstd/kstd/flat_map +++ b/libs/kstd/kstd/flat_map @@ -19,12 +19,24 @@ namespace kstd 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; @@ -64,6 +76,19 @@ namespace kstd , 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. diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp index 6c57173..9df03bb 100644 --- a/libs/kstd/kstd/flat_map.test.cpp +++ b/libs/kstd/kstd/flat_map.test.cpp @@ -62,6 +62,33 @@ SCENARIO("Flat Map modifiers", "[flat_map]") 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); + } + } + } } } -- cgit v1.2.3