From abaaf61af598c32906a94b3db81865352e77ef70 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:24:54 +0200 Subject: kstd: add SSO string replacement draft --- libs/kstd/CMakeLists.txt | 2 +- libs/kstd/kstd/bits/basic_string.hpp | 1336 ++++++++++++++++++++ libs/kstd/kstd/bits/basic_string.test.cpp | 1876 +++++++++++++++++++++++++++++ libs/kstd/kstd/bits/char_traits.hpp | 119 -- libs/kstd/kstd/bits/char_traits.test.cpp | 120 -- libs/kstd/kstd/string | 320 +---- 6 files changed, 3218 insertions(+), 555 deletions(-) create mode 100644 libs/kstd/kstd/bits/basic_string.hpp create mode 100644 libs/kstd/kstd/bits/basic_string.test.cpp delete mode 100644 libs/kstd/kstd/bits/char_traits.hpp delete mode 100644 libs/kstd/kstd/bits/char_traits.test.cpp diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 36bacfc..99ce0c8 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -77,7 +77,7 @@ if(BUILD_TESTING) "kstd/flat_map.test.cpp" "kstd/format.test.cpp" "kstd/vector.test.cpp" - "kstd/bits/char_traits.test.cpp" + "kstd/bits/basic_string.test.cpp" "kstd/bits/observer_ptr.test.cpp" "kstd/test_support/os_panic.test.cpp" "kstd/string.test.cpp" diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp new file mode 100644 index 0000000..4b32e31 --- /dev/null +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -0,0 +1,1336 @@ +#ifndef KSTD_BITS_BASIC_STRING_HPP +#define KSTD_BITS_BASIC_STRING_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + using char_traits = std::char_traits; // NOLINT + + template, + typename Allocator = kstd::allocator> + struct basic_string + + { + //! The character traits of this string. + using traits_type = Traits; + //! The type of character stored in this string. + using value_type = CharacterType; + //! The allocator type used for memory allocations by this string. + using allocator_type = Allocator; + //! The type of all sizes in this string. + using size_type = std::allocator_traits::size_type; + //! The integer type used to represent differences in positions in this string. + using difference_type = std::allocator_traits::difference_type; + //! The type of a reference to an element of this string. + using reference = value_type &; + //! The type of a reference to a constant element of this string. + using const_reference = value_type const &; + //! The type of a pointer to an element of this string. + using pointer = std::allocator_traits::pointer; + //! The type of a pointer to a constant element of this string. + using const_pointer = std::allocator_traits::const_pointer; + //! The type of an iterator to elements of this string. + using iterator = pointer; + //! The type of an iterator to constant elements of this string. + using const_iterator = const_pointer; + //! The type of an iterator to elements of this string, preseting the in reverse order. + using reverse_iterator = std::reverse_iterator; + //! The type of an iterator to constant elements of this string, preseting the in reverse order. + using const_reverse_iterator = std::reverse_iterator; + + //! A constant representing a non-existant position in this string. + constexpr auto static inline npos = static_cast(-1); + + //! Construct an empty string. + basic_string() noexcept(noexcept(Allocator{})) + : basic_string{Allocator{}} + {} + + //! Construct an empty basic string with the given allocator + //! + //! @param allocator The allocator to use for memory allocations in this string. + explicit basic_string(Allocator const & allocator) + : m_allocator{allocator} + , m_data{m_sso_buffer} + { + update_length(0); + } + + //! Construct a string with a single character. + //! + //! @param character The character to initialize the string with. + explicit constexpr basic_string(value_type character) + : basic_string{Allocator{}} + { + m_data[0] = character; + update_length(1); + } + + //! Construct a string with a given number of copies of a given character. + //! + //! @param count The number of characters. + //! @param value The value to use. + //! @param allocator The allocator to use for memory allocations in this string. + constexpr basic_string(size_type count, CharacterType value, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + if (count > capacity()) + { + update_data(allocate_buffer(count, true)); + } + + std::ranges::fill_n(m_data, count, value); + update_length(count); + } + + //! Construct a string with the elements in the range denoted by two given iterators. + //! + //! @param first The iterator to the first element. + //! @param last The iterator beyond the last element. + //! @param allocator The allocator to use for memory allocation in this string. + //! @tparam InputIterator an iterator type satisfying the InputIterator concept. + template + constexpr basic_string(InputIterator first, InputIterator last, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + forward_from(first, last); + } + + //! Construct a string with the contents of a given range. + //! + //! @param range The range to use elements from. + //! @param allocator The allocator to use for memory allocation in this string. + template Range> + constexpr basic_string(kstd::from_range_t, Range && range, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + forward_from(std::ranges::begin(std::forward(range)), std::ranges::end(std::forward(range))); + } + + //! Construct a string with the contents of a given range. + //! + //! @param source The start of the source range. + //! @param count The number of elements to copy. + //! @param allocator The allocator to use for memory allocation in this string. + constexpr basic_string(CharacterType const * source, size_type count, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + if (!source && count) + { + os::panic("Tried to construct string from null pointer!"); + } + + if (count > capacity()) + { + update_data(allocate_buffer(count, true)); + } + + traits_type::copy(m_data, source, count); + update_length(count); + } + + //! Construct a string from a given null-terminated, C-style string. + constexpr basic_string(CharacterType const * string, Allocator const & allocator = Allocator{}) + : basic_string{string, traits_type::length(string), allocator} + {} + + //! Construction from nullptr is prohibited. + constexpr basic_string(std::nullptr_t) = delete; + + //! Construct a string from a string view like object. + //! + //! @param view_like An object that is implicitly convertible to a string view. + //! @param allocator The allocator to use for memory allocation in this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + explicit constexpr basic_string(StringViewLike const & view_like, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + auto converted = std::basic_string_view{view_like}; + forward_from(std::ranges::begin(converted), std::ranges::end(converted)); + } + + //! Construct a string from a substring of a string view like object. + //! + //! @param view_like An object that is implicitly convertible to a string view. + //! @param from The starting position in the string view like object. + //! @param count The maximum number of character to copy from the string view like object. + //! @param allocator The allocator to use for memory allocation in this string. + template + requires std::is_convertible_v> + constexpr basic_string(StringViewLike const & view_like, size_type from, size_type count, + Allocator const & allocator = Allocator{}) + : basic_string{std::basic_string_view{view_like}.substr(from, count), allocator} + {} + + //! Construct a new string as a copy of an existing one. + //! + //! @param other The string to copy from. + constexpr basic_string(basic_string const & other) + : basic_string{other.data(), other.size(), + std::allocator_traits::select_on_container_copy_construction(other.m_allocator)} + {} + + //! Construct a new string by moving from an existing one. + //! + //! @param other The string to move from. + constexpr basic_string(basic_string && other) noexcept + : m_allocator{std::move(other.m_allocator)} + , m_length{other.m_length} + { + if (other.in_sso_state()) + { + m_data = m_sso_buffer; + traits_type::copy(m_data, other.m_data, m_length + 1); + } + else + { + m_data = other.m_data; + other.m_data = other.m_sso_buffer; + m_heap_capacity = other.m_heap_capacity; + } + + other.update_length(0); + } + + //! Destroy a string, releasing all resources. + constexpr ~basic_string() + { + release_buffer(); + } + + //! Replace the contents of this string with a copy of the contents of another one. + //! + //! @param other The string to copy from. + //! @return A reference to this string. + constexpr auto operator=(basic_string const & other) -> basic_string & + { + if (this == &other) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) + { + if (m_allocator != other.m_allocator) + { + release_buffer(); + m_data = m_sso_buffer; + } + m_allocator = other.m_allocator; + } + + if (other.in_sso_state()) + { + if (!in_sso_state()) + { + release_buffer(); + m_data = m_sso_buffer; + } + traits_type::copy(m_sso_buffer, other.m_sso_buffer, other.m_length + 1); + } + else + { + if (in_sso_state() || m_heap_capacity < other.m_length) + { + update_data(allocate_buffer(other.m_length, true)); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + else + { + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + } + + update_length(other.m_length); + return *this; + } + + //! Replace the contents of this string by moving from another one. + //! + //! @param other The string to move from. + //! @return A reference to this string. + constexpr auto operator=(basic_string && other) noexcept( + std::allocator_traits::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) -> basic_string & + { + if (this == &other) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + if (m_allocator != other.m_allocator) + { + release_buffer(); + m_data = m_sso_buffer; + } + m_allocator = std::move(other.m_allocator); + } + + if (other.in_sso_state()) + { + if (!in_sso_state()) + { + release_buffer(); + m_data = m_sso_buffer; + } + traits_type::copy(m_sso_buffer, other.m_sso_buffer, other.m_length + 1); + } + else if (std::allocator_traits::propagate_on_container_move_assignment::value || + m_allocator == other.m_allocator) + { + release_buffer(); + m_data = std::exchange(other.m_data, other.m_sso_buffer); + m_heap_capacity = other.m_heap_capacity; + } + else + { + if (in_sso_state() || m_heap_capacity < other.m_length) + { + update_data(allocate_buffer(other.m_length, true)); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + else + { + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + } + + update_length(other.m_length); + other.update_length(0); + + return *this; + } + + //! Replace the contents of this string with a copy of a given C-style string. + //! + //! @param string The C-style string to copy from. + //! @return A reference to this string. + constexpr auto operator=(CharacterType const * string) -> basic_string & + { + if (string == nullptr) + { + os::panic("Tried to construct string from null pointer!"); + } + + auto incoming_length = traits_type::length(string); + + if (capacity() < incoming_length) + { + auto new_buffer = allocate_buffer(incoming_length, true); + traits_type::copy(new_buffer.data(), string, incoming_length + 1); + update_data(new_buffer); + } + else + { + traits_type::move(m_data, string, incoming_length); + } + + update_length(incoming_length); + return *this; + } + + //! Replace the contents of this string with a given character. + //! + //! @param character The character to use to replace the contents of this string. + //! @return A reference to this string. + constexpr auto operator=(CharacterType character) -> basic_string & + { + m_data[0] = character; + update_length(1); + return *this; + } + + //! Replace the contents of this string with the elements of a given initializer list. + //! + //! @param list The initializer list to copy from. + //! @return A reference to this string. + constexpr auto operator=(std::initializer_list list) -> basic_string & + { + if (list.size() > capacity()) + { + update_data(allocate_buffer(list.size(), true)); + } + + for (auto i = 0uz; auto elem : list) + { + traits_type::assign(m_data[i], elem); + } + + update_length(list.size()); + return *this; + } + + //! Replace the contents of this string with the one from a given strinv view like object. + //! + //! @param view_like The string view like object to copy from. + //! @return A reference to this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + constexpr auto operator=(StringViewLike const & view_like) -> basic_string + { + auto converted = std::basic_string_view{view_like}; + + if (converted.size() > capacity()) + { + update_data(allocate_buffer(converted.size(), true)); + } + + traits_type::copy(m_data, converted.data(), converted.size()); + update_length(converted.size()); + return *this; + } + + //! Assignment from nullptr is prohibited. + constexpr auto operator=(std::nullptr_t) = delete; + + //! Get the allocator used by this string. + //! + //! @return A copy of the allocator used by this string. + [[nodiscard]] constexpr auto get_allocator() const noexcept(noexcept(Allocator{Allocator{}})) -> allocator_type + { + return m_allocator; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto at(size_type position) -> reference + { + if (position >= size()) + { + os::panic("Invalid index in string element access"); + } + + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto at(size_type position) const -> const_reference + { + if (position >= size()) + { + os::panic("Invalid index in string element access"); + } + + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto operator[](size_type position) noexcept -> reference + { + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto operator[](size_type position) const noexcept -> const_reference + { + return m_data[position]; + } + + //! Get the first character in this string. + //! + //! @return A reference to first character. + [[nodiscard]] constexpr auto front() noexcept -> reference + { + return (*this)[0]; + } + + //! Get the first character in this string. + //! + //! @return A reference to first character. + [[nodiscard]] constexpr auto front() const noexcept -> const_reference + { + return (*this)[0]; + } + + //! Get the last character in this string. + //! + //! @return A reference to last character. + [[nodiscard]] constexpr auto back() noexcept -> reference + { + return (*this)[size() - 1]; + } + + //! Get the last character in this string. + //! + //! @return A reference to last character. + [[nodiscard]] constexpr auto back() const noexcept -> const_reference + { + return (*this)[size() - 1]; + } + + //! Get a pointer to the underlying data of this string. + //! + //! @return A pointer to the underlying data of this string. + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the underlying data of this string. + //! + //! @return A pointer to the underlying data of this string. + [[nodiscard]] constexpr auto data() const noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the null-terminated underlying data of this string. + //! + //! @return A pointer to the null-terminated underlying data of this string. + [[nodiscard]] constexpr auto c_str() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the null-terminated underlying data of this string. + //! + //! @return A pointer to the null-terminated underlying data of this string. + [[nodiscard]] constexpr auto c_str() const noexcept -> pointer + { + return m_data; + } + + //! Get a non-modifiable view of this string. + operator std::basic_string_view() const noexcept + { + return {data(), size()}; + } + + //! Get an iterator to the beginning of this string. + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return data(); + } + + //! Get a const iterator to the beginning of this string. + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return data(); + } + + //! Get a const iterator to the beginning of this string. + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return data(); + } + + //! Get an iterator to the end of this string. + [[nodiscard]] constexpr auto end() noexcept -> iterator + { + return data() + size(); + } + + //! Get a const iterator to the end of this string. + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get a const iterator to the end of this string. + [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get a reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator + { + return end(); + } + + //! Get a const reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator + { + return end(); + } + + //! Get a const reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator + { + return end(); + } + + //! Get a reverse iterator to the end of this string. + [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator + { + return begin(); + } + + //! Get a const reverse iterator to the end of this string. + [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator + { + return begin(); + } + + //! Get a const reverse iterator to the end of this string. + [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator + { + return begin(); + } + + //! Check if this string is empty. + //! + //! @return @p true iff. the string is empty, @p false otherwise. + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return size() == 0uz; + } + + //! Get the size of this string. + //! + //! @return the number of characters in this string. + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_length; + } + + //! Get the length of this string. + //! + //! @return the number of characters in this string. + [[nodiscard]] constexpr auto length() const noexcept -> size_type + { + return size(); + } + + //! Get the maximum possible size for this string. + //! + //! @return the maximum number of elements this string can hold. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type + { + return std::numeric_limits::max() / sizeof(value_type) - 1; + } + + //! Get the currently allocated capacity of this string + //! + //! @return the number of elements that can be stored in this string without causing a reallocation. + [[nodiscard]] constexpr auto capacity() const noexcept -> size_type + { + return (in_sso_state() ? std::size(m_sso_buffer) : m_heap_capacity) - 1; + } + + //! Clear the content of this string. + constexpr auto clear() -> void + { + update_length(0); + } + + //! Insert a sequence of identical characters at the given index. + //! + //! @param index The position to insert the characters at. + //! @param count The number of characters to insert. + //! @param character The character to insert. + //! @return A reference to this string. + constexpr auto insert(size_type index, size_type count, CharacterType character) -> basic_string & + { + if (index > size()) + { + os::panic("Index out of bounds while inserting into string"); + } + + if (count == 0uz) + { + return *this; + } + + auto new_length = size() + count; + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length, true); + traits_type::copy(new_buffer.data(), m_data, index); + std::ranges::fill_n(new_buffer.data() + index, count, character); + traits_type::copy(new_buffer.data() + index + count, m_data + index, size() - index); + update_data(new_buffer); + } + else + { + traits_type::move(m_data + index + count, m_data + index, size() - index); + std::ranges::fill_n(m_data + index, count, character); + } + + update_length(new_length); + return *this; + } + + //! Insert a C-style string into this string at a given position. + //! + //! @param index The position to insert the data at. + //! @param string The string to insert. + constexpr auto insert(size_type index, CharacterType const * string) -> basic_string & + { + if (string == nullptr) + { + os::panic("Tried to insert nullptr string"); + } + + return do_insert(index, std::basic_string_view{string}); + } + + //! Append a character to this string. + //! + //! @param character The character to append. + constexpr auto push_back(CharacterType character) -> void + { + auto new_length = m_length + 1; + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + update_data(new_buffer); + } + + m_data[m_length] = character; + update_length(new_length); + } + + //! Remove the last character of this string. + constexpr auto pop_back() -> void + { + if (m_length > 0) + { + update_length(m_length - 1); + } + } + + //! Append a number of copies of a given character to this string. + //! + //! @param count The number of copies to append. + //! @param character The character to append. + //! @return A reference to this string. + constexpr auto append(size_type count, CharacterType character) -> basic_string & + { + if (count == 0) + { + return *this; + } + + auto new_length = m_length + count; + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + update_data(new_buffer); + } + + std::ranges::fill_n(m_data + m_length, count, character); + update_length(new_length); + + return *this; + } + + //! Append a substring of a given C-style string to this string. + //! + //! @param string The string to append. + //! @param count The number of characters of that string to append. + //! @return A reference to this string. + constexpr auto append(CharacterType const * string, std::size_t count) -> basic_string & + { + if (string == nullptr) + { + os::panic("Attempted to append nullptr string"); + } + + if (count == 0) + { + return *this; + } + + auto new_length = m_length + count; + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + traits_type::copy(new_buffer.data() + m_length, string, count); + update_data(new_buffer); + } + else + { + traits_type::copy(m_data + m_length, string, count); + } + + update_length(new_length); + + return *this; + } + + //! Append a C-style string to this string. + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto append(CharacterType const * string) -> basic_string & + { + return append(string, traits_type::length(string)); + } + + //! Append to this string by copying from a given string view like object + //! + //! @param view_like The string view like object to copy from. + //! @return A reference to this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + constexpr auto append(StringViewLike const & view_like) -> basic_string & + { + auto converted = std::basic_string_view{view_like}; + return append(converted.data(), converted.length()); + } + + //! Append a string to this string. + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto append(basic_string const & string) -> basic_string & + { + return append(string.c_str(), string.length()); + } + + //! Append a string to this string + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto operator+=(basic_string const & string) -> basic_string & + { + return append(string); + } + + //! Append a character to this string + //! + //! @param character The character to append. + //! @return A reference to this string. + constexpr auto operator+=(value_type character) -> basic_string & + { + push_back(character); + return *this; + } + + //! Swap the contents of this string with another one. + //! + //! @param other The string to swap contents with. + constexpr auto + swap(basic_string & other) noexcept(std::allocator_traits::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value) -> void + { + using std::swap; + + if (this == &other) + { + return; + } + + if constexpr (std::allocator_traits::propagate_on_container_swap::value) + { + swap(m_allocator, other.m_allocator); + } + + if (!in_sso_state() && !other.in_sso_state()) + { + swap(m_data, other.m_data); + swap(m_heap_capacity, other.m_heap_capacity); + } + else if (in_sso_state() && !other.in_sso_state()) + { + auto new_data = std::exchange(other.m_data, other.m_sso_buffer); + auto new_heap_capacity = other.m_heap_capacity; + + traits_type::copy(other.m_data, m_data, m_length + 1); + + m_data = new_data; + m_heap_capacity = new_heap_capacity; + } + else if (!in_sso_state() && other.in_sso_state()) + { + auto new_data = std::exchange(m_data, m_sso_buffer); + auto new_heap_capacity = m_heap_capacity; + + traits_type::copy(m_data, other.m_data, other.m_length + 1); + + other.m_data = new_data; + other.m_heap_capacity = new_heap_capacity; + } + else + { + auto temporary_buffer = std::array{}; + + traits_type::copy(temporary_buffer.data(), m_data, m_length + 1); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + traits_type::copy(other.m_data, temporary_buffer.data(), m_length + 1); + } + + swap(m_length, other.m_length); + } + + //! Concatenate two string. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, basic_string const & rhs) -> basic_string + { + auto result = lhs; + result.append(rhs); + return result; + } + + //! Concatenate a string an a C-style string. + //! + //! @param lhs The right hand side string. + //! @param rhs The left hand side C-style string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, CharacterType const * rhs) -> basic_string + { + auto result = lhs; + result.append(rhs); + return result; + } + + //! Create a new string by appending a character to a string. + //! + //! @param lhs The string. + //! @param rhs The character to append. + //! @return A new string with the character appended to the string. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, CharacterType rhs) -> basic_string + { + auto result = lhs; + result.push_back(rhs); + return result; + } + + //! Contcatenate a C-style string and a string. + //! + //! @param lhs The left hand side C-style string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(const_pointer lhs, basic_string const & rhs) -> basic_string + { + auto result = rhs; + result.insert(0, lhs); + return result; + } + + //! Create a new string by prepending a character to a string. + //! + //! @param lhs The character to prepend. + //! @param rhs The string + //! @return A new string with the character prepended to the string. + [[nodiscard]] constexpr friend auto operator+(CharacterType lhs, basic_string const & rhs) -> basic_string + { + auto result = rhs; + result.insert(0, lhs); + return result; + } + + //! Contcatenate two strings. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, basic_string && rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Contcatenate two strings. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, basic_string const & rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Contcatenate a string and a C-style string. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side C-style string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, const_pointer rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Append a character to a string. + //! + //! @param lhs The string to append to. + //! @param rhs The character to append. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, value_type rhs) -> basic_string + { + lhs.push_back(rhs); + return std::move(lhs); + } + + //! Contcatenate two strings + //! + //! @param lhs The string to prepend. + //! @param rhs The string to append to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, lhs); + return std::move(rhs); + } + + //! Contcatenate a C-style string and a string. + //! + //! @param lhs The C-style string to prepend. + //! @param rhs The string to prepend to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(const_pointer lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, lhs); + return std::move(rhs); + } + + //! Prepend a character to a string. + //! + //! @param lhs The character to prepend. + //! @param rhs The string to prepend to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(value_type lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, 1, lhs); + return std::move(rhs); + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, basic_string const & rhs) noexcept -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), lhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, + std::basic_string_view const & rhs) noexcept + -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), rhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(std::basic_string_view const & lhs, + basic_string const & rhs) noexcept -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), lhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, const_pointer rhs) noexcept -> bool + { + return lhs == std::basic_string_view{rhs}; + } + + [[nodiscard]] constexpr friend auto operator==(const_pointer lhs, basic_string const & rhs) noexcept -> bool + { + return std::basic_string_view{lhs} == rhs; + } + + [[nodiscard]] constexpr friend auto operator<=>(basic_string const & lhs, basic_string const & rhs) noexcept + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto + operator<=>(basic_string const & lhs, std::basic_string_view const & rhs) noexcept + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto operator<=>(std::basic_string_view const & lhs, + basic_string const & rhs) noexcept -> std::strong_ordering + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto operator<=>(basic_string const & lhs, const_pointer rhs) noexcept + { + return lhs <=> std::basic_string_view{rhs}; + } + + [[nodiscard]] constexpr friend auto operator<=>(const_pointer lhs, basic_string const & rhs) noexcept + { + return std::basic_string_view{lhs} <=> rhs; + } + + private: + //! Allocate a buffer for the data. + //! + //! @param count The of character to reserve space for, excluding the termininating null. + //! @param exact Allocate space for the exact number of characters requested. + //! @return A span describing the newly allocated buffer. + [[nodiscard]] constexpr auto allocate_buffer(size_type count, bool exact = false) -> std::span + { + if (count > max_size()) + { + os::panic("Tried to allocate more memory than possible"); + } + + auto to_allocate = (exact ? count : std::min(std::max(count, 2 * capacity()), max_size())) + 1; + return {std::allocator_traits::allocate(m_allocator, to_allocate), to_allocate}; + } + + //! Insert a string view into this string at a given position. + //! + //! @param index The position to insert the data at. + //! @param view The string view to insert. + constexpr auto do_insert(size_type index, std::basic_string_view const & view) + -> basic_string & + { + if (index > size()) + { + os::panic("Index out of bounds while inserting into string"); + } + + if (view.size() == 0uz) + { + return *this; + } + + auto new_length = size() + view.size(); + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, index); + traits_type::copy(new_buffer.data() + index, view.data(), view.size()); + traits_type::copy(new_buffer.data() + index + view.size(), m_data + index, size() - index + 1); + update_data(new_buffer); + } + else + { + traits_type::move(m_data + index + view.size(), m_data + index, size() - index); + + if (is_aliased(view.data())) // NOLINT(bugprone-suspicious-stringview-data-usage) + { + auto offset = view.data() - m_data; + + if (offset >= index) + { + traits_type::copy(m_data + index, m_data + offset + view.size(), view.size()); + } + else + { + traits_type::copy(m_data + index, m_data + offset, view.size()); + } + } + else + { + traits_type::copy(m_data + index, view.data(), view.size()); + } + } + + update_length(new_length); + return *this; + } + + //! Copy elements from a range described by an input iterator pair. + //! + //! @param first An iterator pointing to the first element to copy. + //! @param last An iterator pointing beyond the last elemento copy. + template + requires std::input_iterator> + constexpr auto forward_from(InputIterator && first, Sentinel last) -> void + { + auto input_size = 0uz; + + for (; first != last && input_size < capacity(); ++first, ++input_size) + { + traits_type::assign(m_data[input_size], *std::forward(first)); + } + + for (; first != last; ++first, ++input_size) + { + if (input_size == capacity()) + { + auto new_buffer = allocate_buffer(input_size + 1); + traits_type::move(new_buffer.data(), m_data, input_size + 1); + update_data(new_buffer); + } + + traits_type::assign(m_data[input_size], *std::forward(first)); + } + + update_length(input_size); + } + + //! Copy elements from a range described by a forward iterator pair. + //! + //! @param first An iterator pointing to the first element to copy. + //! @param last An iterator pointing beyond the last elemento copy. + template + requires std::forward_iterator> + constexpr auto forward_from(ForwardIterator && first, Sentinel last) -> void + { + auto input_size = std::ranges::distance(std::forward(first), last); + + if (input_size > capacity()) + { + auto new_buffer = allocate_buffer(input_size, true); + update_data(new_buffer); + } + + for (auto i = 0uz; first != last; ++first, ++i) + { + traits_type::assign(m_data[i], *std::forward(first)); + } + + update_length(input_size); + } + + //! Check if this string is currently in its SSO state. + //! + //! @return @p true iff. this string is in its SSO state, @p false otherwise. + [[nodiscard]] constexpr auto in_sso_state() const noexcept -> bool + { + return m_data == m_sso_buffer; + } + + //! Check if the a given pointer aliases into the internal string buffer. + //! + //! @param pointer The pointer to check for aliasing. + //! @return @p true iff. the given pointer aliases into the internal string buffer, @p false otherwise. + [[nodiscard]] constexpr auto is_aliased(const_pointer pointer) const noexcept -> bool + { + if consteval + { + for (auto i = 0uz; i < m_length; ++i) + { + if (pointer == m_data + i) + { + return true; + } + } + return false; + } + else + { + return pointer >= m_data && pointer <= m_data + m_length; + } + } + + //! Release the current buffer of this string. + //! + //! This function can be called even if this string is currently in tis SSO state. + [[nodiscard]] constexpr auto release_buffer() + { + if (!in_sso_state()) + { + std::allocator_traits::deallocate(m_allocator, m_data, m_heap_capacity); + } + } + + //! Update the data pointer + //! + //! @param new_buffer The new buffer for this string. + constexpr auto update_data(std::span new_buffer) -> void + { + release_buffer(); + m_data = new_buffer.data(); + m_heap_capacity = new_buffer.size(); + } + + //! Update the recorded length of this string and ensure null-termination. + //! + //! @param new_length The new length of this string. + constexpr auto update_length(size_type new_length) noexcept -> void + { + m_length = new_length; + traits_type::assign(m_data[m_length], value_type{}); + } + + //! The allocator used for heap allocations. + [[no_unique_address]] allocator_type m_allocator; + + //! The pointer to the start of the string data. + //! + //! When the SSO is active, this points to the internal SSO buffer m_sso_buffer. Otherwise, this points to the + //! heap allocation backing this string. + pointer m_data; + + //! The length of this string. + size_type m_length; + + //! The SSO buffer, or the capacity of this string if SSO is not active. + union + { + value_type m_sso_buffer[15 / sizeof(value_type) + 1]; // NOLINT(modernize-avoid-c-arrays) + size_type m_heap_capacity; + }; + }; + + template + constexpr auto swap(basic_string & lhs, + basic_string & rhs) -> void + { + lhs.swap(rhs); + } + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/bits/basic_string.test.cpp b/libs/kstd/kstd/bits/basic_string.test.cpp new file mode 100644 index 0000000..dba0ea0 --- /dev/null +++ b/libs/kstd/kstd/bits/basic_string.test.cpp @@ -0,0 +1,1876 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using char_string = kstd::basic_string; + +SCENARIO("Basic string of char initialization and construction", "[string]") +{ + THEN("All type aliases are present") + { + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>::size_type>); + REQUIRE( + std::is_same_v>::difference_type>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>::pointer>); + REQUIRE(std::is_same_v>::const_pointer>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>); + } + + THEN("npos is present") + { + REQUIRE(char_string::npos == char_string::size_type(-1)); + } + + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto s = char_string{}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to zero") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to zero") + { + REQUIRE(s.length() == 0); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing with 10 copies of the letter 'a'") + { + auto s = char_string{10, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with 20 copies of the letter 'a'") + { + auto s = char_string{20, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with an input iterator pair of 10 characters") + { + auto source = std::istringstream{"abcdefghij"}; + auto s = char_string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with an input iterator pair of 20 characters") + { + auto source = std::istringstream{"abcdefghijABCDEFGHIJ"}; + auto s = char_string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is greater than or equal to 20") + { + REQUIRE(s.capacity() >= 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with a forward iterator pair of 10 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + auto s = char_string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with a forward iterator pair of 20 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; + auto s = char_string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a null pointer with zero size") + { + auto s = char_string{static_cast(nullptr), 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a from a null pointer and non-zero size") + { + THEN("a panic is raised") + { + REQUIRE_THROWS_AS((char_string{static_cast(nullptr), 1}), kstd::tests::os_panic); + } + } + + WHEN("constructing from a non-null pointer and zero size") + { + auto ptr = "abcd"; + auto s = char_string{ptr, 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a non-null pointer and a size of 4") + { + auto ptr = "abcd"; + auto s = char_string{ptr, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a non-null pointer and a size of 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = char_string{ptr, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a C-style string of length 4") + { + auto ptr = "abcd"; + auto s = char_string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a C-style string of length 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = char_string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = char_string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = char_string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a substring of a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = char_string{view, 1, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 3") + { + REQUIRE(s.size() == 3); + } + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 3") + { + REQUIRE(std::strlen(s.c_str()) == 3); + } + + THEN("the string converts to a string view of length 3") + { + auto view = static_cast(s); + REQUIRE(view.length() == 3); + } + } + + WHEN("constructing from a substring of a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = char_string{view, 1, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 19") + { + REQUIRE(s.size() == 19); + } + + THEN("the length is equal to 19") + { + REQUIRE(s.length() == 19); + } + + THEN("the capacity is equal to 19") + { + REQUIRE(s.capacity() == 19); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 19") + { + REQUIRE(std::strlen(s.c_str()) == 19); + } + + THEN("the string converts to a string view of length 19") + { + auto view = static_cast(s); + REQUIRE(view.length() == 19); + } + } + } + + GIVEN("An existing short string, of length 10") + { + auto other = char_string{"abcdefghij"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } + + GIVEN("An existing short string, of length 20") + { + auto other = char_string{"abcdefghijABCDEFGHIJ"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } +} + +SCENARIO("Basic string assignment", "[string]") +{ + GIVEN("Two short strings") + { + auto s1 = char_string{"abcd"}; + auto s2 = char_string{"defghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 7); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 15); + } + } + } + + GIVEN("One short and one long string") + { + auto s1 = char_string{"abcd"}; + auto s2 = char_string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("Two long strings") + { + auto s1 = char_string{"abcdefghijABCDEFGH"}; + auto s2 = char_string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is still 20") + { + REQUIRE(s2.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 18); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 18); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("An short string") + { + auto s = char_string{"abcd"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 5") + { + s = "defgh"; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning a new, longer, c-style string of length 20") + { + s = "ABCDEFGHIJabcdefghij"; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 5") + { + s = {'d', 'e', 'f', 'g', 'h'}; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning an longer initializer list of length 20") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 5") + { + using namespace std::string_view_literals; + + s = "defgh"sv; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning from a longer string view of length 20") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghij"sv; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + } + + GIVEN("An long string") + { + auto s = char_string{"abcdefghijABCDEFGHIJ"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 22") + { + s = "ABCDEFGHIJabcdefghijkl"; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 22") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', + 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 22") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghijkl"sv; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + } +} diff --git a/libs/kstd/kstd/bits/char_traits.hpp b/libs/kstd/kstd/bits/char_traits.hpp deleted file mode 100644 index b9a4a03..0000000 --- a/libs/kstd/kstd/bits/char_traits.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef KSTD_BITS_CHAR_TRAITS_HPP -#define KSTD_BITS_CHAR_TRAITS_HPP - -// IWYU pragma: private, include - -#include - -#include -#include - -namespace kstd -{ - - template - struct char_traits; - - template<> - struct char_traits - { - using char_type = char; - using int_type = int; - using off_type = long long; - - constexpr auto static assign(char_type & lhs, char_type const & rhs) noexcept -> void - { - lhs = rhs; - } - - constexpr auto static assign(char_type * destination, std::size_t count, char_type value) noexcept -> char_type * - { - std::ranges::fill_n(destination, static_cast(count), value); - return destination; - } - - constexpr auto static eq(char_type lhs, char_type rhs) noexcept -> bool - { - return static_cast(lhs) == static_cast(rhs); - } - - constexpr auto static lt(char_type lhs, char_type rhs) noexcept -> bool - { - return static_cast(lhs) < static_cast(rhs); - } - - constexpr auto static move(char_type * destination, char_type const * source, std::size_t count) noexcept - -> char_type * - { - std::ranges::move_backward(source, source + count, destination); - return destination; - } - - constexpr auto static copy(char_type * destination, char_type const * source, std::size_t count) noexcept - -> char_type * - { - std::ranges::copy(source, source + count, destination); - return destination; - } - - constexpr auto static compare(char_type const * lhs, char_type const * rhs, std::size_t count) noexcept -> int - { - for (auto i = 0uz; i < count; ++i) - { - auto comparison_result = static_cast(lhs[i]) - static_cast(rhs[i]); - if (comparison_result) - { - return comparison_result; - } - } - return 0; - } - - constexpr auto static length(char_type const * string) noexcept -> std::size_t - { - auto string_length = 0uz; - for (; string[string_length] != char_type{}; ++string_length) - ; - return string_length; - } - - constexpr auto static find(char_type const * string, std::size_t count, char_type const & needle) noexcept - -> char_type const * - { - auto found = std::ranges::find(string, string + count, needle); - if (found == string + count) - { - return nullptr; - } - return found; - } - - constexpr auto static to_char_type(int_type value) noexcept -> char_type - { - return static_cast(value); - } - - constexpr auto static to_int_type(char_type value) noexcept -> int_type - { - return static_cast(value); - } - - constexpr auto static eq_int_type(int_type lhs, int_type rhs) noexcept -> bool - { - return lhs == rhs; - } - - constexpr auto static eof() noexcept -> int_type - { - return -1; - } - - constexpr auto static not_eof(int_type value) noexcept -> int_type - { - return value == eof() ? 0 : value; - } - }; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/bits/char_traits.test.cpp b/libs/kstd/kstd/bits/char_traits.test.cpp deleted file mode 100644 index 4864c37..0000000 --- a/libs/kstd/kstd/bits/char_traits.test.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include - -#include - -#include - -SCENARIO("Character traits for char", "[string]") -{ - using traits = kstd::char_traits; - - THEN("Type aliases") - { - REQUIRE(std::is_same_v::char_type>); - REQUIRE(std::is_same_v::int_type>); - REQUIRE(std::is_same_v::off_type>); - } - - GIVEN("two unequal characters 'a' and 'B'") - { - auto const a = 'a'; - auto const B = 'B'; - - WHEN("comparing the two") - { - THEN("eq(a, a) returns true") - { - REQUIRE(traits::eq(a, a)); - } - - THEN("eq(a, B) returns false") - { - REQUIRE_FALSE(traits::eq(a, B)); - } - - THEN("eq(B, a) returns false") - { - REQUIRE_FALSE(traits::eq(B, a)); - } - - THEN("lt(a,a) returns false") - { - REQUIRE_FALSE(traits::lt(a, a)); - } - - THEN("lt(a,B) returns false") - { - REQUIRE_FALSE(traits::lt(a, B)); - } - - THEN("lt(B,a) returns true") - { - REQUIRE(traits::lt(B, a)); - } - - THEN("eq_int_type(a, a) returns true") - { - REQUIRE(traits::eq_int_type(a, a)); - } - - THEN("eq_int_type(a, B) returns false") - { - REQUIRE_FALSE(traits::eq_int_type(a, B)); - } - - THEN("eq_int_type(B, a) returns false") - { - REQUIRE_FALSE(traits::eq_int_type(B, a)); - } - } - } - - GIVEN("two unequal strings 'abc', 'DEFG'") - { - auto const abc = "abc"; - auto const abDe = "abDe"; - - WHEN("comparing the two") - { - THEN("compare(abc, abc, 3) returns 0") - { - REQUIRE(traits::compare(abc, abc, 3) == 0); - } - - THEN("compare(abc, abDe, 2) returns 0") - { - REQUIRE(traits::compare(abc, abDe, 2) == 0); - } - - THEN("compare(abc, abDe, 3) returns >0") - { - REQUIRE(traits::compare(abc, abDe, 3) > 0); - } - - THEN("compare(abDe, abc, 3) returns <0") - { - REQUIRE(traits::compare(abDe, abc, 3) < 0); - } - } - } - - GIVEN("The string 'abcd'") - { - auto const abcd = "abcd"; - - THEN("length(abcd) returns 4") - { - REQUIRE(traits::length(abcd) == 4); - } - - THEN("find(abcd, 4, b) returns a pointer to b") - { - REQUIRE(*traits::find(abcd, 4, 'b') == 'b'); - } - - THEN("find(abcd, 4, q) returns a null pointer") - { - REQUIRE(traits::find(abcd, 4, 'q') == nullptr); - } - } -} diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string index 9343b42..de254a1 100644 --- a/libs/kstd/kstd/string +++ b/libs/kstd/kstd/string @@ -1,330 +1,20 @@ #ifndef KSTD_STRING_HPP #define KSTD_STRING_HPP +#include // IWYU pragma: export #include #include -#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. - */ - explicit 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; - - 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. - */ - [[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()) - { - m_storage.reserve(size() + view.size() + 1); - 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(static_cast(other)); - } - - /** - * @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; - } - - //! 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 static_cast(*this) == other; - } - - 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; - } + using string = basic_string; /** * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and @@ -356,11 +46,11 @@ namespace kstd } template<> - struct formatter : formatter + struct formatter : formatter { auto format(string const & str, format_context & context) const -> void { - formatter::format(static_cast(str), context); + formatter::format(str.data(), context); } }; -- cgit v1.2.3