diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string new file mode 100644 index 0000000..99a7247 --- /dev/null +++ b/include/kf/stl/basic_string @@ -0,0 +1,889 @@ +#pragma once +#include +#include +#include +#include +#include + +#if _KERNEL_MODE && _ITERATOR_DEBUG_LEVEL > 0 +#error "_ITERATOR_DEBUG_LEVEL must not be greater than 0" +#endif + +namespace kf +{ + ////////////////////////////////////////////////////////////////////////// + // string - the Wrapper for std::string that allows to use it in + // exception-free environments. + + template> + class basic_string + { + public: + using string_type = std::basic_string, Allocator>; + using traits_type = string_type::traits_type; + using allocator_type = string_type::allocator_type; + + using value_type = string_type::value_type; + using size_type = string_type::size_type; + using difference_type = string_type::difference_type; + using pointer = string_type::pointer; + using const_pointer = string_type::const_pointer; + using reference = string_type::reference; + using const_reference = string_type::const_reference; + + using iterator = string_type::iterator; + using const_iterator = string_type::const_iterator; + + using reverse_iterator = string_type::reverse_iterator; + using const_reverse_iterator = string_type::const_reverse_iterator; + + static constexpr size_type npos = string_type::npos; + + // + // Construction and assignment + // + + constexpr basic_string() noexcept = default; + + basic_string(const basic_string&) = delete; + basic_string& operator=(const basic_string&) = delete; + + constexpr basic_string(basic_string&& other) noexcept = default; + constexpr basic_string& operator=(basic_string&& other) noexcept = default; + + constexpr [[nodiscard]] NTSTATUS assign(size_type count, const T& value) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(count, value); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const basic_string& other) noexcept + { + if (auto status = reallocateGrowth(other.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(other.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const basic_string& other, size_type pos, size_type count = npos) noexcept + { + if (pos > other.size()) + { + return assign(0, T{}); + } + + const auto available = other.size() - pos; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(other.m_string, pos, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const T* s, size_type count) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const T* s) noexcept + { + const auto len = traits_type::length(s); + return assign(s, len); + } + + constexpr operator std::string_view() const noexcept + { + return std::string_view(m_string.data(), m_string.size()); + } + + constexpr void swap(basic_string& other) noexcept + { + m_string.swap(other.m_string); + } + + // + // Element access + // + + constexpr std::optional> at(size_type pos) noexcept + { + return pos < size() ? std::optional(std::ref(m_string[pos])) : std::nullopt; + } + + constexpr std::optional> at(size_type pos) const noexcept + { + return pos < size() ? std::optional(std::ref(m_string[pos])) : std::nullopt; + } + + constexpr T& operator[](size_type pos) noexcept + { + return m_string[pos]; + } + + constexpr const T& operator[](size_type pos) const noexcept + { + return m_string[pos]; + } + + constexpr T& front() noexcept + { + return m_string.front(); + } + + constexpr const T& front() const noexcept + { + return m_string.front(); + } + + constexpr T& back() noexcept + { + return m_string.back(); + } + + constexpr const T& back() const noexcept + { + return m_string.back(); + } + + constexpr T* data() + { + return m_string.data(); + } + + constexpr const T* data() const noexcept + { + return m_string.data(); + } + + constexpr const T* c_str() const noexcept + { + return m_string.c_str(); + } + + // + // Iterators + // + + constexpr iterator begin() noexcept + { + return m_string.begin(); + } + + constexpr const_iterator begin() const noexcept + { + return m_string.begin(); + } + + constexpr const_iterator cbegin() const noexcept + { + return m_string.cbegin(); + } + + constexpr iterator end() noexcept + { + return m_string.end(); + } + + constexpr const_iterator end() const noexcept + { + return m_string.end(); + } + + constexpr const_iterator cend() const noexcept + { + return m_string.cend(); + } + + constexpr reverse_iterator rbegin() noexcept + { + return m_string.rbegin(); + } + + constexpr const_reverse_iterator rbegin() const noexcept + { + return m_string.rbegin(); + } + + constexpr const_reverse_iterator crbegin() const noexcept + { + return m_string.crbegin(); + } + + constexpr reverse_iterator rend() noexcept + { + return m_string.rend(); + } + + constexpr const_reverse_iterator rend() const noexcept + { + return m_string.rend(); + } + + constexpr const_reverse_iterator crend() const noexcept + { + return m_string.crend(); + } + + // + // Size and capacity + // + + constexpr bool empty() const noexcept + { + return m_string.empty(); + } + + constexpr size_type size() const noexcept + { + return m_string.size(); + } + + constexpr size_type length() const noexcept + { + return size(); + } + + constexpr size_type max_size() const noexcept + { + return m_string.max_size(); + } + + constexpr NTSTATUS reserve(size_type newCapacity) noexcept + { + if (newCapacity <= m_string.capacity()) + { + return STATUS_SUCCESS; + } + + return reallocateExactly(newCapacity); + } + + constexpr size_type capacity() const noexcept + { + return m_string.capacity(); + } + + constexpr NTSTATUS shrink_to_fit() noexcept + { + if (m_string.size() == m_string.capacity()) + { + return STATUS_SUCCESS; + } + + if (auto status = reallocateExactly(m_string.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.shrink_to_fit(); + return STATUS_SUCCESS; + } + + // + // Comparison + // + + constexpr int compare(const basic_string& str) const noexcept + { + return m_string.compare(str.m_string); + } + + constexpr int compare(size_type pos1, size_type count1, const basic_string& str) const noexcept + { + return m_string.compare(pos1, count1, str.m_string); + } + + constexpr int compare(size_type pos1, size_type count1, const basic_string& str, size_type pos2, size_type count2 = npos) const noexcept + { + return m_string.compare(pos1, count1, str.m_string, pos2, count2); + } + + constexpr int compare(const T* s) const noexcept + { + return m_string.compare(s); + } + + constexpr int compare(size_type pos1, size_type count1, const T* s) const noexcept + { + return m_string.compare(pos1, count1, s); + } + + constexpr int compare(size_type pos1, size_type count1, const T* s, size_type count2) const noexcept + { + return m_string.compare(pos1, count1, s, count2); + } + + constexpr bool operator==(const basic_string& rhs) const noexcept + { + return compare(rhs) == 0; + } + + constexpr bool operator!=(const basic_string& rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr bool operator<(const basic_string& rhs) const noexcept + { + return compare(rhs) < 0; + } + + // Compare with C-style strings (const T*) + constexpr bool operator==(const T* rhs) const noexcept + { + return compare(rhs) == 0; + } + + constexpr bool operator!=(const T* rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr bool operator<(const T* rhs) const noexcept + { + return compare(rhs) < 0; + } + + // + // Modifiers + // + + constexpr void clear() noexcept + { + m_string.clear(); + } + + // insert (iterator-based) + constexpr std::optional insert(const_iterator pos, const T& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, value); + } + + constexpr std::optional insert(const_iterator pos, T&& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, std::move(value)); + } + + constexpr std::optional insert(const_iterator pos, size_type count, const T& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, count, value); + } + + // insert (index-based) + constexpr [[nodiscard]] NTSTATUS insert(size_type index, size_type count, T ch) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, count, ch); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const T* s, size_type count) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const T* s) noexcept + { + return insert(index, s, traits_type::length(s)); + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const basic_string& str) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + str.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const basic_string& str, size_type index_str, size_type count = npos) noexcept + { + if (index_str > str.size()) + { + return STATUS_SUCCESS; + } + + const auto available = str.size() - index_str; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(m_string.size() + clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, str.m_string, index_str, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS push_back(const T& value) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return status; + } + + m_string.push_back(value); + + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS push_back(T&& value) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return status; + } + + m_string.push_back(std::move(value)); + + return STATUS_SUCCESS; + } + + constexpr void pop_back() noexcept + { + m_string.pop_back(); + } + + constexpr [[nodiscard]] NTSTATUS append(size_t count, T ch) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(count, ch); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const basic_string& str) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + str.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const basic_string& str, size_type pos, size_type count = npos) noexcept + { + if (pos > str.size()) + { + return STATUS_SUCCESS; + } + + const auto available = str.size() - pos; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(m_string.size() + clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(str.m_string, pos, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const T* s, size_t count) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const T* s) noexcept + { + return append(s, traits_type::length(s)); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(const basic_string& str) noexcept + { + return append(str); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(const T* s) noexcept + { + return append(s); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(T ch) noexcept + { + return append(1, ch); + } + + constexpr [[nodiscard]] NTSTATUS resize(size_type count) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.resize(count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS resize(size_type count, const T& value) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.resize(count, value); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS erase(size_type index = 0, size_type count = npos) noexcept + { + m_string.erase(index, count); + return STATUS_SUCCESS; + } + + constexpr iterator erase(const_iterator pos) noexcept + { + return m_string.erase(pos); + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept + { + return m_string.erase(first, last); + } + + constexpr [[nodiscard]] NTSTATUS replace(size_t pos, size_t count, const basic_string& str) noexcept + { + size_t removed = 0; + if (pos <= size()) + { + const auto available = size() - pos; + removed = (count == npos || count > available) ? available : count; + } + + const auto newSize = size() - removed + str.size(); + if (auto status = reallocateGrowth(newSize); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(pos, count, str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos, size_type count, const T* s, size_type count2) noexcept + { + size_type removed = 0; + if (pos <= size()) + { + const auto available = size() - pos; + removed = (count == npos || count > available) ? available : count; + } + + const auto newSize = size() - removed + count2; + if (auto status = reallocateGrowth(newSize); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(pos, count, s, count2); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos, size_type count, const T* s) noexcept + { + return replace(pos, count, s, traits_type::length(s)); + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos1, size_type count1, const basic_string& str, size_t pos2, size_t count2 = npos) noexcept + { + if (pos2 > str.size()) + { + return replace(pos1, count1, static_cast(nullptr), size_type{0}); + } + + const auto available = str.size() - pos2; + const auto clamped = (count2 == npos || count2 > available) ? available : count2; + return replace(pos1, count1, str.m_string.data() + pos2, clamped); + } + + constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, const basic_string& str) noexcept + { + const auto idx = static_cast(first - m_string.begin()); + const auto cnt = static_cast(last - first); + return replace(idx, cnt, str); + } + + constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, size_type count2, T ch) noexcept + { + const auto cnt = static_cast(last - first); + if (auto status = reallocateGrowth(size() - cnt + count2); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(first, last, count2, ch); + return STATUS_SUCCESS; + } + + // + // String operations + // + + constexpr size_type copy(T* dest, size_type count, size_type pos = 0) const noexcept + { + if (pos > size()) + { + return 0; + } + + const auto available = size() - pos; + const auto rcount = (count > available) ? available : count; + traits_type::copy(dest, m_string.data() + pos, rcount); + return rcount; + } + + constexpr basic_string substr(size_type pos = 0, size_type count = npos) const noexcept + { + basic_string result; + if (pos > size()) + { + return result; + } + + const auto available = size() - pos; + const auto rcount = (count == npos || count > available) ? available : count; + if (!NT_SUCCESS(result.reserve(rcount))) + { + return result; // return empty on allocation failure + } + + result.m_string.append(m_string.data() + pos, rcount); + return result; + } + + // find/rfind and friends + constexpr size_type find(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find(str.m_string, pos); + } + + constexpr size_type find(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find(s, pos, count); + } + + constexpr size_type find(const T* s, size_type pos = 0) const noexcept + { + return m_string.find(s, pos); + } + + constexpr size_type find(T ch, size_type pos = 0) const noexcept + { + return m_string.find(ch, pos); + } + + constexpr size_type rfind(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.rfind(str.m_string, pos); + } + + constexpr size_type rfind(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.rfind(s, pos, count); + } + + constexpr size_type rfind(const T* s, size_type pos = npos) const noexcept + { + return m_string.rfind(s, pos); + } + + constexpr size_type rfind(T ch, size_type pos = npos) const noexcept + { + return m_string.rfind(ch, pos); + } + + constexpr size_type find_first_of(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find_first_of(str.m_string, pos); + } + + constexpr size_type find_first_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_first_of(s, pos, count); + } + + constexpr size_type find_first_of(const T* s, size_type pos = 0) const noexcept + { + return m_string.find_first_of(s, pos); + } + + constexpr size_type find_first_of(T ch, size_type pos = 0) const noexcept + { + return m_string.find_first_of(ch, pos); + } + + constexpr size_type find_first_not_of(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(str.m_string, pos); + } + + constexpr size_type find_first_not_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_first_not_of(s, pos, count); + } + + constexpr size_type find_first_not_of(const T* s, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(s, pos); + } + + constexpr size_type find_first_not_of(T ch, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(ch, pos); + } + + constexpr size_type find_last_of(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.find_last_of(str.m_string, pos); + } + + constexpr size_type find_last_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_last_of(s, pos, count); + } + + constexpr size_type find_last_of(const T* s, size_type pos = npos) const noexcept + { + return m_string.find_last_of(s, pos); + } + + constexpr size_type find_last_of(T ch, size_type pos = npos) const noexcept + { + return m_string.find_last_of(ch, pos); + } + + constexpr size_type find_last_not_of(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(str.m_string, pos); + } + + constexpr size_type find_last_not_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_last_not_of(s, pos, count); + } + + constexpr size_type find_last_not_of(const T* s, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(s, pos); + } + + constexpr size_type find_last_not_of(T ch, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(ch, pos); + } + + private: + constexpr NTSTATUS reallocateGrowth(size_type newSize) noexcept + { + if (newSize <= m_string.capacity()) + { + return STATUS_SUCCESS; + } + + return moveInternal(calculateGrowth(newSize)); + } + + constexpr NTSTATUS reallocateExactly(size_type newSize) noexcept + { + return moveInternal(newSize); + } + + constexpr NTSTATUS moveInternal(size_type newCapacity) noexcept + { + string_type newString; + newString.reserve(newCapacity); + if (newString.capacity() < newCapacity) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + m_string.swap(newString); + return STATUS_SUCCESS; + } + + constexpr size_type calculateGrowth(size_type required) noexcept + { + const auto oldCapacity = capacity(); + const auto max = max_size(); + if (oldCapacity > max - oldCapacity / 2) + { + return max; // geometric growth would overflow + } + + const auto geometric = oldCapacity + oldCapacity / 2; + if (geometric < required) + { + return required; // geometric growth would be insufficient + } + + return geometric; // geometric growth is sufficient + } + + private: + string_type m_string{}; + }; +} diff --git a/include/kf/stl/string b/include/kf/stl/string new file mode 100644 index 0000000..d0494c1 --- /dev/null +++ b/include/kf/stl/string @@ -0,0 +1,368 @@ +#pragma once +#include +#include + +namespace kf +{ + template + using string = kf::basic_string; + + template + using wstring = kf::basic_string; + +#ifdef __cpp_lib_char8_t + template + using u8string = kf::basic_string; +#endif // defined(__cpp_lib_char8_t) + + template + using u16string = kf::basic_string; + + template + using u32string = kf::basic_string; + + namespace utils + { + template + void skipWhitespace(It& it, It end) + { + while (it != end && (*it == CharT(' ') || *it == CharT('\t'))) + { + ++it; + } + } + + template + bool isNegative(It& it, It end) + { + bool isNegative = false; + + if (it != end && (*it == CharT('+') || *it == CharT('-'))) + { + if (*it == CharT('-')) + { + isNegative = true; + } + + it++; + } + + return isNegative; + } + + template + void skipPrefix(It& it, It end, int base) + { + if (base == 16) + { + if ((it + 1 != end) && *it == CharT('0') && (*(it + 1) == CharT('x') || *(it + 1) == CharT('X'))) + { + it += 2; + } + } + + if (base == 8) + { + if (it != end && *it == CharT('0')) + { + ++it; + } + } + } + + template + int detectBase(It& it, It end) + { + if (it != end && *it == CharT('0')) + { + ++it; + + if (it != end && (*it == CharT('x') || *it == CharT('X'))) + { + ++it; + return 16; + } + + return 8; + } + + return 10; + } + + template + int charToDigit(CharT c) + { + if (c >= CharT('0') && c <= CharT('9')) + { + return c - CharT('0'); + } + + if (c >= CharT('a') && c <= CharT('z')) + { + return c - CharT('a') + 10; + } + + if (c >= CharT('A') && c <= CharT('Z')) + { + return c - CharT('A') + 10; + } + + return -1; + } + + } + + // + // string to numeric + // + + template + [[nodiscard]] NTSTATUS string_to_numeric( + _In_ const basic_string& str, + _Out_ T& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + requires (std::is_integral_v) + { + if (base != 0 && (base < 2 || base > 36)) + { + return STATUS_INVALID_PARAMETER; + } + + auto it = str.cbegin(); + auto end = str.cend(); + + utils::skipWhitespace(it, end); + + bool negative = utils::isNegative(it, end); + + if constexpr (!std::is_signed_v) + { + if (negative) + { + return STATUS_INTEGER_OVERFLOW; + } + } + + utils::skipPrefix(it, end, base); + + if (base == 0) + { + base = utils::detectBase(it, end); + } + + T temp = 0; + bool anyDigit = false; + + for (; it != end; ++it) + { + int digit = utils::charToDigit(*it); + + if (digit < 0 || digit >= base) + { + break; + } + + anyDigit = true; + + if (negative) + { + if (temp < ((std::numeric_limits::min)() + digit) / base) + { + return STATUS_INTEGER_OVERFLOW; + } + temp = temp * base - digit; + } + else + { + if (temp > ((std::numeric_limits::max)() - digit) / base) + { + return STATUS_INTEGER_OVERFLOW; + } + temp = temp * base + digit; + } + } + + if (!anyDigit) + { + return STATUS_INVALID_PARAMETER; + } + + if (idx) + { + *idx = static_cast(it - str.cbegin()); + } + + result = temp; + + return STATUS_SUCCESS; + } + + // ===== string ===== + + template + [[nodiscard]] NTSTATUS stoi( + _In_ const string& str, + _Out_ int& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stol( + _In_ const string& str, + _Out_ long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoul( + _In_ const string& str, + _Out_ unsigned long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoll( + _In_ const string& str, + _Out_ long long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoull( + _In_ const string& str, + _Out_ unsigned long long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + // ===== wstring ===== + + template + [[nodiscard]] NTSTATUS stoi( + _In_ const wstring& str, + _Out_ int& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stol( + _In_ const wstring& str, + _Out_ long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoul( + _In_ const wstring& str, + _Out_ unsigned long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoll( + _In_ const wstring& str, + _Out_ long long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + template + [[nodiscard]] NTSTATUS stoull( + _In_ const wstring& str, + _Out_ unsigned long long& result, + _Out_opt_ size_t* idx = nullptr, + _In_ int base = 10 + ) + { + return string_to_numeric(str, result, idx, base); + } + + // + // numeric to string + // + + // Max 20 digits (unsigned long long) or 19 digits + sign (signed long long), plus null terminator + constexpr size_t kMaxLongLongStrLength = 21; + + template + [[nodiscard]] NTSTATUS to_string(T value, _Out_ kf::string& str) + requires (std::is_integral_v) + { + char buffer[kMaxLongLongStrLength]{}; + NTSTATUS status; + + if constexpr (std::is_signed_v) + { + status = RtlStringCbPrintfA(buffer, kMaxLongLongStrLength, "%lld", static_cast(value)); + } + else + { + status = RtlStringCbPrintfA(buffer, kMaxLongLongStrLength, "%llu", static_cast(value)); + } + if (!NT_SUCCESS(status)) + { + return status; + } + + return str.assign(buffer); + } + + template + [[nodiscard]] NTSTATUS to_wstring(T value, _Out_ kf::wstring& str) + requires (std::is_integral_v) + { + wchar_t buffer[kMaxLongLongStrLength]{}; + NTSTATUS status; + + if constexpr (std::is_signed_v) + { + status = RtlStringCbPrintfW(buffer, kMaxLongLongStrLength * sizeof(wchar_t), L"%lld", static_cast(value)); + } + else + { + status = RtlStringCbPrintfW(buffer, kMaxLongLongStrLength * sizeof(wchar_t), L"%llu", static_cast(value)); + } + if (!NT_SUCCESS(status)) + { + return status; + } + + return str.assign(buffer); + } +} diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp new file mode 100644 index 0000000..bbd4bb9 --- /dev/null +++ b/test/BasicStringTest.cpp @@ -0,0 +1,1185 @@ +#include "pch.h" +#include + +SCENARIO("basic_string: ñonstruction and assignment") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("Hello, World!")); + + WHEN("assign(size_type count, const T& value) is called") + { + auto status = str.assign(5, 'A'); + + THEN("the string should contain the repeated character") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "AAAAA"); + } + } + + WHEN("assign(const T* s) is called") + { + auto status = str.assign("New Content"); + + THEN("the string should contain the new content") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "New Content"); + } + } + + WHEN("assign(const T* s, size_type count) is called") + { + auto status = str.assign("Partial", 4); + + THEN("the string should contain the partial content") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "Part"); + } + } + + WHEN("assign(const basic_string& other) is called") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("Another String")); + auto status = str.assign(other); + + THEN("the string should be assigned the content of the other string") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "Another String"); + } + } + + WHEN("operator std::string_view() const is called") + { + std::string_view view(str); + + THEN("the view should reflect the string content") + { + REQUIRE(view == "Hello, World!"); + } + } + + WHEN("swap(basic_string& other) is called") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("Other")); + str.swap(other); + + THEN("the contents should be swapped") + { + REQUIRE(str == "Other"); + REQUIRE(other == "Hello, World!"); + } + } + + WHEN("assign(const T* s) is called on a wide string") + { + kf::basic_string wstr; + auto status = wstr.assign(L"WideText"); + + THEN("the wide string should contain the new content") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(wstr == L"WideText"); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("operator std::string_view() const is called") + { + std::string_view view(str); + + THEN("the view should be empty") + { + REQUIRE(view.empty()); + } + } + + WHEN("assign(const T* s) is called with empty C-string") + { + auto status = str.assign(""); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + } + + WHEN("assign(size_type count, const T& value) is called with zero count") + { + auto status = str.assign(0, 'Z'); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("assign(const T* s, size_type count) is called with zero count") + { + auto status = str.assign("abc", 0); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("assign(const basic_string& other) is called with empty other") + { + kf::basic_string other; + auto status = str.assign(other); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("swap(basic_string& other) is called with non-empty other") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("X")); + str.swap(other); + + THEN("the empty string takes the content and other becomes empty") + { + REQUIRE(str == "X"); + REQUIRE(other.empty()); + } + } + } +} + +SCENARIO("basic_string: Element access") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abc")); + + WHEN("at(size_type pos) is called") + { + auto refOpt = str.at(1); + + THEN("it should be 'b' and writable") + { + REQUIRE(refOpt.has_value()); + REQUIRE(refOpt->get() == 'b'); + refOpt->get() = 'B'; + REQUIRE(str == "aBc"); + } + } + + WHEN("at(size_type pos) is called on a const basic_string with out-of-range index") + { + const auto& cstr = str; + auto refOpt = cstr.at(100); + + THEN("it should be empty") + { + REQUIRE(!refOpt.has_value()); + } + } + + WHEN("operator[](size_type) is called") + { + REQUIRE(str[0] == 'a'); + str[2] = 'C'; + + THEN("the characters should reflect updates via operator[]") + { + REQUIRE(str == "abC"); + } + } + + WHEN("front() is called") + { + THEN("front should return the first character") + { + REQUIRE(str.front() == 'a'); + } + } + + WHEN("back() is called") + { + THEN("back should return the last character") + { + REQUIRE(str.back() == 'c'); + } + } + + WHEN("data() is called") + { + THEN("data should not be null") + { + REQUIRE(str.data() != nullptr); + } + } + + WHEN("c_str() is called") + { + THEN("c_str should not be null") + { + REQUIRE(str.c_str() != nullptr); + } + } + + WHEN("at(size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"xyz")); + auto refOpt = wstr.at(0); + + THEN("wide element should be accessible") + { + REQUIRE(refOpt.has_value()); + REQUIRE(refOpt->get() == L'x'); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("at(size_type pos) is called") + { + auto refOpt = str.at(0); + + THEN("it should be empty") + { + REQUIRE(!refOpt.has_value()); + } + } + + WHEN("data() is called") + { + THEN("data should not be null and point to a null terminator") + { + REQUIRE(str.data() != nullptr); + REQUIRE(*str.data() == '\0'); + } + } + + WHEN("c_str() is called") + { + THEN("c_str should not be null and be an empty string") + { + REQUIRE(str.c_str() != nullptr); + REQUIRE(str.c_str()[0] == '\0'); + } + } + } +} + +SCENARIO("basic_string: Iterators") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abcd")); + + WHEN("begin() and end() are used to iterate") + { + std::array acc{}; + size_t i = 0; + for (auto it = str.begin(); it != str.end(); ++it) + { + acc[i++] = *it; + } + + THEN("accumulated sequence matches") + { + REQUIRE(acc[0] == 'a'); + REQUIRE(acc[1] == 'b'); + REQUIRE(acc[2] == 'c'); + REQUIRE(acc[3] == 'd'); + } + } + + WHEN("rbegin() and rend() are used to iterate") + { + std::array acc{}; + size_t i = 0; + for (auto it = str.rbegin(); it != str.rend(); ++it) + { + acc[i++] = *it; + } + + THEN("accumulated sequence matches reverse") + { + REQUIRE(acc[0] == 'd'); + REQUIRE(acc[1] == 'c'); + REQUIRE(acc[2] == 'b'); + REQUIRE(acc[3] == 'a'); + } + } + + WHEN("cbegin() is called on a const basic_string") + { + const auto& cstr = str; + auto it = cstr.cbegin(); + + THEN("dereferencing works") + { + REQUIRE(*it == 'a'); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("begin() and end() are used to iterate") + { + THEN("begin equals end for empty string") + { + REQUIRE(str.begin() == str.end()); + } + } + + WHEN("rbegin() and rend() are used to iterate") + { + THEN("rbegin equals rend for empty string") + { + REQUIRE(str.rbegin() == str.rend()); + } + } + + WHEN("cbegin() and cend() are used on a const basic_string") + { + const auto& cstr = str; + THEN("cbegin equals cend for empty string") + { + REQUIRE(cstr.cbegin() == cstr.cend()); + } + } + } +} + +SCENARIO("basic_string: Size and capacity") +{ + GIVEN("an empty basic_string") + { + kf::basic_string str; + REQUIRE(str.empty()); + + WHEN("reserve(size_type newCapacity) is called") + { + auto st1 = str.reserve(32); + const auto cap = str.capacity(); + + THEN("reserve should succeed and capacity grow") + { + REQUIRE_NT_SUCCESS(st1); + REQUIRE(cap >= 32); + } + } + + WHEN("shrink_to_fit() is called") + { + REQUIRE_NT_SUCCESS(str.assign("12345")); + auto status = str.shrink_to_fit(); + const auto capNow = str.capacity(); + const auto sizeNow = str.size(); + + THEN("capacity should be equal to size after shrink") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(capNow == sizeNow); + } + } + + WHEN("length() is called") + { + REQUIRE_NT_SUCCESS(str.assign("xx")); + const auto len = str.length(); + const auto sz = str.size(); + + THEN("length equals size") + { + REQUIRE(len == sz); + } + } + + WHEN("max_size() is called") + { + REQUIRE_NT_SUCCESS(str.assign("x")); + const auto max = str.max_size(); + const auto sz = str.size(); + + THEN("max_size is sane") + { + REQUIRE(max >= sz); + } + } + + WHEN("resize(size_type count, const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("hi")); + auto status = str.resize(5, 'x'); + + THEN("the string should be grown with fill value") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "hixxx"); + } + } + + WHEN("resize(size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("hi")); + auto status = str.resize(2); + + THEN("the string should be shrunk") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "hi"); + } + } + } + + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + + WHEN("size() is called") + { + THEN("size equals number of characters") + { + REQUIRE(str.size() == 6); + } + } + + WHEN("capacity() is called") + { + THEN("capacity is at least size") + { + REQUIRE(str.capacity() >= str.size()); + } + } + + WHEN("reserve(size_type newCapacity) is called") + { + auto stRes = str.reserve(64); + + THEN("reserve succeeds and content preserved") + { + REQUIRE_NT_SUCCESS(stRes); + REQUIRE(str.capacity() >= 64); + REQUIRE(str == "abcdef"); + } + } + + WHEN("shrink_to_fit() is called") + { + REQUIRE_NT_SUCCESS(str.reserve(64)); + auto status = str.shrink_to_fit(); + const auto capNow = str.capacity(); + const auto szNow = str.size(); + + THEN("capacity equals size and content preserved") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(capNow == szNow); + REQUIRE(str == "abcdef"); + } + } + + WHEN("resize(size_type count) is called") + { + auto status = str.resize(3); + + THEN("string should be truncated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abc"); + } + } + + WHEN("length() is called") + { + THEN("length equals size") + { + REQUIRE(str.length() == str.size()); + } + } + + WHEN("max_size() is called") + { + THEN("max_size is at least current size") + { + REQUIRE(str.max_size() >= str.size()); + } + } + } +} + +SCENARIO("basic_string: Comparison") +{ + GIVEN("two basic_strings with content") + { + kf::basic_string a, b; + REQUIRE_NT_SUCCESS(a.assign("alpha")); + REQUIRE_NT_SUCCESS(b.assign("beta")); + + WHEN("operator==(const basic_string&) is called") + { + kf::basic_string c; + REQUIRE_NT_SUCCESS(c.assign("alpha")); + + THEN("they are equal when contents match") + { + REQUIRE(a == c); + } + } + + WHEN("operator!=(const basic_string&) is called") + { + THEN("they are not equal when contents differ") + { + REQUIRE(a != b); + } + } + + WHEN("operator<(const basic_string&) is called") + { + THEN("they compare lexicographically") + { + REQUIRE(a < b); + } + } + + WHEN("compare(const basic_string&) is called") + { + const auto cmp = a.compare(b); + + THEN("compare is negative when lhs < rhs") + { + REQUIRE(cmp < 0); + } + } + + WHEN("operator==(const T*) is called") + { + THEN("C-string equality works") + { + REQUIRE(a == "alpha"); + } + } + + WHEN("operator!=(const T*) is called") + { + THEN("C-string inequality works") + { + REQUIRE(a != "alphabet"); + } + } + + WHEN("operator<(const T*) is called") + { + THEN("C-string less-than works") + { + REQUIRE(a < "b"); + } + } + + WHEN("operator==(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide equality works with wide literals") + { + REQUIRE(w == L"zeta"); + } + } + + WHEN("operator!=(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide inequality works with wide literals") + { + REQUIRE(w != L"eta"); + } + } + + WHEN("operator<(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide less-than works with wide literals") + { + REQUIRE(w < L"zzzz"); + } + } + } + + GIVEN("one of basic_strings with content") + { + kf::basic_string a, b; + REQUIRE_NT_SUCCESS(a.assign("alpha")); + // b is empty + + WHEN("operator==(const basic_string&) is called") + { + THEN("content string is not equal to empty string") + { + REQUIRE(a != b); + } + } + + WHEN("operator<(const basic_string&) is called") + { + THEN("empty string is less than content string") + { + REQUIRE(b < a); + } + } + + WHEN("compare(const basic_string&) is called") + { + const auto cmp1 = a.compare(b); + const auto cmp2 = b.compare(a); + + THEN("compare reflects ordering vs empty") + { + REQUIRE(cmp1 > 0); + REQUIRE(cmp2 < 0); + } + } + } +} + +SCENARIO("basic_string: Modifiers") +{ + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("clear() is called") + { + REQUIRE_NT_SUCCESS(str.assign("core")); + str.clear(); + + THEN("string is empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + } + + WHEN("push_back(const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto status = str.push_back('d'); + + THEN("element is appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abcd"); + } + } + + WHEN("pop_back() is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcd")); + str.pop_back(); + + THEN("last char removed") + { + REQUIRE(str == "abc"); + } + } + + WHEN("insert(const_iterator pos, const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABCD")); + auto itOpt = str.insert(str.begin() + 2, 'x'); // ABxCD + + THEN("iterator is returned and string modified") + { + REQUIRE(itOpt.has_value()); + REQUIRE(str == "ABxCD"); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABxCD")); + auto first = str.begin() + 1; // B + auto last = str.begin() + 3; // x + auto after = str.erase(first, last); // remove Bx -> ACD + + THEN("erase returns iterator to next element") + { + REQUIRE(*after == 'C'); + REQUIRE(str == "ACD"); + } + } + + WHEN("insert(size_type index, size_type count, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto status = str.insert(1, 2, 'Y'); // aYYbc + + THEN("characters inserted by count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "aYYbc"); + } + } + + WHEN("insert(size_type index, const T* s) is called") + { + REQUIRE_NT_SUCCESS(str.assign("aYYbc")); + auto status = str.insert(0, "--"); // --aYYbc + + THEN("C-string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--aYYbc"); + } + } + + WHEN("insert(size_type index, const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--aYYbc")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("[]")); + auto status = str.insert(2, other); // --[]aYYbc + + THEN("basic_string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--[]aYYbc"); + } + } + + WHEN("insert(size_type index, const basic_string&, size_type index_str, size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--[]aYYbc")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("[]")); + auto status = str.insert(2, other, 1, 1); // --] []aYYbc -> --][]aYYbc + + THEN("substring from basic_string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--][]aYYbc"); + } + } + + WHEN("append(size_t count, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo")); + auto status = str.append(3, '!'); + + THEN("characters appended by count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!"); + } + } + + WHEN("append(const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!")); + kf::basic_string more; + REQUIRE_NT_SUCCESS(more.assign("bar")); + auto status = str.append(more); + + THEN("basic_string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!bar"); + } + } + + WHEN("append(const T* s, size_t count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!bar")); + auto status = str.append("XYZ", 2); + + THEN("partial C-string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXY"); + } + } + + WHEN("operator+=(const T*) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!barXY")); + auto status = (str += "Z"); + + THEN("C-string concatenated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXYZ"); + } + } + + WHEN("operator+=(T) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!barXYZ")); + auto status = (str += '!'); + + THEN("single character concatenated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXYZ!"); + } + } + + WHEN("replace(size_t pos, size_t count, const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("0123456789")); + kf::basic_string tmp; + REQUIRE_NT_SUCCESS(tmp.assign("AA")); + auto status = str.replace(2, 3, tmp); // 01 + AA + 56789 + + THEN("substring replaced with basic_string") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("AA") == 2); + } + } + + WHEN("replace(size_type pos, size_type count, const T* s, size_type count2) is called") + { + REQUIRE_NT_SUCCESS(str.assign("01AA56789")); + auto status = str.replace(2, 2, "XX", 2); + + THEN("substring replaced with C-string and count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("XX") == 2); + } + } + + WHEN("replace(const_iterator first, const_iterator last, size_type count2, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--XX56789")); + auto first = str.begin() + 2; + auto last = first + 2; + auto status = str.replace(first, last, 3, '*'); + + THEN("replacement should be applied") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("***", 0) != decltype(str)::npos); + } + } + + WHEN("erase(size_type index, size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("erase")); + REQUIRE_NT_SUCCESS(str.erase(1, 2)); // e + se -> ese + + THEN("substring erased by index/count") + { + REQUIRE(str == "ese"); + } + } + + WHEN("erase(const_iterator pos) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ese")); + auto it = str.erase(str.begin()); + + THEN("first char erased") + { + REQUIRE(*it == 's'); + REQUIRE(str == "se"); + } + } + } +} + +SCENARIO("basic_string: String operations") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("copy-me")); + + WHEN("copy(T* dest, size_type count, size_type pos) is called") + { + char buf[16] = {}; + auto n = str.copy(buf, 4, 5); // from index 5: 'm','e' + + THEN("buffer contains copied data and count is correct") + { + REQUIRE(n == 2); + REQUIRE(buf[0] == 'm'); + REQUIRE(buf[1] == 'e'); + } + } + + WHEN("substr(size_type pos, size_type count) is called") + { + auto sub1 = str.substr(0, 4); + auto sub2 = str.substr(100, 2); // out of range -> empty + + THEN("substrings should be as expected") + { + REQUIRE(sub1 == "copy"); + REQUIRE(sub2.empty()); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("copy(T* dest, size_type count, size_type pos) is called") + { + char buf[8] = {}; + auto n = str.copy(buf, 4, 0); + + THEN("nothing is copied from an empty string") + { + REQUIRE(n == 0); + REQUIRE(buf[0] == '\0'); + } + } + + WHEN("substr(size_type pos, size_type count) is called") + { + auto sub = str.substr(0, 10); + + THEN("substr of empty is empty") + { + REQUIRE(sub.empty()); + } + } + } +} + +SCENARIO("basic_string: find/rfind and friends") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abracadabra")); + + WHEN("find(const T* s, size_type pos) is called") + { + const auto posSub = str.find("abra"); + + THEN("substring position should match") + { + REQUIRE(posSub == 0); + } + } + + WHEN("find(T ch, size_type pos) is called") + { + const auto posCh = str.find('c'); + + THEN("character position should match") + { + REQUIRE(posCh == 4); + } + } + + WHEN("rfind(const T* s, size_type pos) is called") + { + const auto posRSub = str.rfind("abra"); + + THEN("reverse substring position should match") + { + REQUIRE(posRSub == 7); + } + } + + WHEN("rfind(T ch, size_type pos) is called") + { + const auto posRCh = str.rfind('a'); + + THEN("reverse character position should match") + { + REQUIRE(posRCh == 10); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called") + { + const auto posFirst = str.find_first_of("xyzabc"); + + THEN("it finds any of the given set from the beginning") + { + REQUIRE(posFirst == 0); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called") + { + const auto posLast = str.find_last_of("xyzabc"); + + THEN("it finds any of the given set from the end") + { + REQUIRE(posLast == 10); + } + } + + WHEN("find_first_not_of(const T* s, size_type pos) is called") + { + const auto posFirstNot = str.find_first_not_of("ab"); + + THEN("it skips the matching set from the beginning") + { + REQUIRE(posFirstNot == 2); // 'r' + } + } + + WHEN("find_last_not_of(const T* s, size_type pos) is called") + { + const auto posLastNot = str.find_last_not_of("ar"); + + THEN("it skips the matching set from the end") + { + REQUIRE(posLastNot == 8); // before 'b' + } + } + + WHEN("find(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posWide = wstr.find(L"Wide"); + + THEN("wide substring search works") + { + REQUIRE(posWide == 6); + } + } + + WHEN("find(T ch, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posWCh = wstr.find(L'W'); + + THEN("wide character search works") + { + REQUIRE(posWCh == 6); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posFirst = wstr.find_first_of(L"HW"); + + THEN("wide find_first_of works") + { + REQUIRE(posFirst == 0); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posLast = wstr.find_last_of(L"od"); + + THEN("wide find_last_of works") + { + REQUIRE(posLast == 15); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("find(const T* s, size_type pos) is called") + { + THEN("find on empty returns npos") + { + REQUIRE(str.find("a") == decltype(str)::npos); + } + } + + WHEN("find(T ch, size_type pos) is called") + { + THEN("find on empty returns npos") + { + REQUIRE(str.find('a') == decltype(str)::npos); + } + } + + WHEN("rfind(const T* s, size_type pos) is called") + { + THEN("rfind on empty returns npos") + { + REQUIRE(str.rfind("a") == decltype(str)::npos); + } + } + + WHEN("rfind(T ch, size_type pos) is called") + { + THEN("rfind on empty returns npos") + { + REQUIRE(str.rfind('a') == decltype(str)::npos); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called") + { + THEN("find_first_of on empty returns npos") + { + REQUIRE(str.find_first_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called") + { + THEN("find_last_of on empty returns npos") + { + REQUIRE(str.find_last_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_first_not_of(const T* s, size_type pos) is called") + { + THEN("find_first_not_of on empty returns npos") + { + REQUIRE(str.find_first_not_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_last_not_of(const T* s, size_type pos) is called") + { + THEN("find_last_not_of on empty returns npos") + { + REQUIRE(str.find_last_not_of("abc") == decltype(str)::npos); + } + } + } +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6dccd52..9dcd75b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,6 +59,8 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL AutoSpinLockTest.cpp EResourceSharedLockTest.cpp RecursiveAutoSpinLockTest.cpp + BasicStringTest.cpp + StringTest.cpp ) target_link_libraries(kf-test kf::kf kmtest::kmtest) diff --git a/test/StringTest.cpp b/test/StringTest.cpp new file mode 100644 index 0000000..e173bb5 --- /dev/null +++ b/test/StringTest.cpp @@ -0,0 +1,568 @@ +#include "pch.h" +#include + +SCENARIO("string to numeric conversion") +{ + GIVEN("string with positive decimal") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("42")); + + constexpr int expectedResult = 42; + constexpr size_t expectedIndex = 2; + int result = 0; + size_t index = 0; + + WHEN("stoi with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("parse success and index points after number") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with negative decimal") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("-77")); + + constexpr int expectedResult = -77; + constexpr size_t expectedIndex = 3; + int result = 0; + size_t index = 0; + + WHEN("stoi with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("parse negative success and index points after number") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with negative decimal and non-numeric tail") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("-77tail")); + + constexpr int expectedResult = -77; + constexpr size_t expectedIndex = 3; + int result = 0; + size_t index = 0; + + WHEN("stoi with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("parse negative success and index points at tail's begining") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with octal number") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("075")); + + constexpr int expectedResult = 61; + constexpr size_t expectedIndex = 3; + int result = 0; + size_t index = 0; + + WHEN("stoi base 8 with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index, 8); + + THEN("parse octal success and index is correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with hexadecimal number") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("0x2A")); + + constexpr int expectedResult = 42; + constexpr size_t expectedIndex = 4; + int result = 0; + size_t index = 0; + + WHEN("stoi base 16 with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index, 16); + + THEN("parse hex success and index is correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with hexadecimal number and non-hex char") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("0x2Az")); + + constexpr int expectedResult = 42; + constexpr size_t expectedIndex = 4; + int result = 0; + size_t index = 0; + + WHEN("stoi base 16 with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index, 16); + + THEN("parse hex success and index correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with base 5 number") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("132")); + + constexpr int expectedResult = 42; // 1*25 + 3*5 + 2 + constexpr size_t expectedIndex = 3; + int result = 0; + size_t index = 0; + + WHEN("stoi base 5 with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index, 5); + + THEN("parse base 5 success and index is correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string with base 5 number and non-base 5 char") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("1328")); + + constexpr int expectedResult = 42; // 1*25 + 3*5 + 2 + constexpr size_t expectedIndex = 3; + int result = 0; + size_t index = 0; + + WHEN("stoi base 5 with index is called") + { + NTSTATUS status = kf::stoi(s, result, &index, 5); + + THEN("parse base 5 success and index is correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("empty string") + { + kf::string s; + + int result = 0; + size_t index = 0; + + WHEN("stoi is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("returns invalid parameter") + { + REQUIRE(status == STATUS_INVALID_PARAMETER); + } + } + } + + GIVEN("string with invalid characters") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("abc")); + + int result = 0; + size_t index = 0; + + WHEN("stoi is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("returns invalid parameter") + { + REQUIRE(status == STATUS_INVALID_PARAMETER); + } + } + } + + GIVEN("string causing overflow for int") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("999999999999")); + + int result = 0; + size_t index = 0; + + WHEN("stoi is called") + { + NTSTATUS status = kf::stoi(s, result, &index); + + THEN("returns integer overflow") + { + REQUIRE(status == STATUS_INTEGER_OVERFLOW); + } + } + } + + GIVEN("string positive for unsigned long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("123abc")); + + constexpr unsigned long expectedResult = 123UL; + constexpr size_t expectedIndex = 3; + unsigned long result = 0; + size_t index = 0; + + WHEN("stoul with index is called") + { + NTSTATUS status = kf::stoul(s, result, &index); + + THEN("parse success and index is correct") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string negative for unsigned long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("-42")); + + unsigned long result = 0; + size_t index = 0; + + WHEN("stoul is called") + { + NTSTATUS status = kf::stoul(s, result, &index); + + THEN("returns integer overflow") + { + REQUIRE(status == STATUS_INTEGER_OVERFLOW); + } + } + } + + GIVEN("string with min long long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("-9223372036854775808")); + + constexpr long long expectedResult = (std::numeric_limits::min)(); + constexpr size_t expectedIndex = 20; + long long result = 0; + size_t index = 0; + + WHEN("stoll is called") + { + NTSTATUS status = kf::stoll(s, result, &index); + + THEN("parse min value success") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string which overflows long long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("9223372036854775808")); + + long long result = 0; + size_t index = 0; + + WHEN("stoll is called") + { + NTSTATUS status = kf::stoll(s, result, &index); + + THEN("returns integer overflow") + { + REQUIRE(status == STATUS_INTEGER_OVERFLOW); + } + } + } + + GIVEN("string overflow unsigned long long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("18446744073709551615")); // unsigned long long max + + constexpr unsigned long long expectedResult = (std::numeric_limits::max)(); + constexpr size_t expectedIndex = 20; + unsigned long long result = 0; + size_t index = 0; + + WHEN("stoull is called") + { + NTSTATUS status = kf::stoull(s, result, &index); + + THEN("parse value success") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("string overflow unsigned long long") + { + kf::string s; + REQUIRE_NT_SUCCESS(s.assign("18446744073709551616")); // unsigned long long max + 1 + + unsigned long long result = 0; + size_t index = 0; + + WHEN("stoull is called") + { + NTSTATUS status = kf::stoull(s, result, &index); + + THEN("returns integer overflow") + { + REQUIRE(status == STATUS_INTEGER_OVERFLOW); + } + } + } + + GIVEN("wstring with decimal positive") + { + kf::wstring ws; + REQUIRE_NT_SUCCESS(ws.assign(L"42")); + + constexpr long long expectedResult = 42; + constexpr size_t expectedIndex = 2; + int result = 0; + size_t index = 0; + + WHEN("stoi is called") + { + NTSTATUS status = kf::stoi(ws, result, &index); + + THEN("parse success and index points after number") + { + REQUIRE(status == STATUS_SUCCESS); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("wstring with hexadecimal negative") + { + kf::wstring ws; + REQUIRE_NT_SUCCESS(ws.assign(L"-0xFF")); + + constexpr long long expectedResult = -255LL; + constexpr size_t expectedIndex =5; + long long result = 0; + size_t index = 0; + + WHEN("stoll with base 0 is called") + { + NTSTATUS status = kf::stoll(ws, result, &index, 0); + + THEN("parse success and index points after number") + { + REQUIRE(status == STATUS_SUCCESS); + REQUIRE(result == expectedResult); + REQUIRE(index == expectedIndex); + } + } + } + + GIVEN("wstring with invalid chars") + { + kf::wstring ws; + REQUIRE_NT_SUCCESS(ws.assign(L"abc")); + + unsigned long long result = 0; + size_t index = 0; + + + WHEN("stoull is called") + { + NTSTATUS status = kf::stoull(ws, result, &index); + + THEN("return invalid parameter, result and index = 0") + { + REQUIRE(status == STATUS_INVALID_PARAMETER); + REQUIRE(result == 0); + REQUIRE(index == 0); + } + } + } +} + +SCENARIO("to_string for numeric values") +{ + kf::string buffer; + + GIVEN("positive int value") + { + constexpr int value = 42; + constexpr const char* expectedStr = "42"; + + WHEN("to_string is called") + { + NTSTATUS status = to_string(value, buffer); + + THEN("conversion succeeds") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } + + GIVEN("negative int value") + { + constexpr int value = -77; + constexpr const char* expectedStr = "-77"; + + WHEN("to_string is called") + { + NTSTATUS status = to_string(value, buffer); + + THEN("conversion succeeds") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } + + GIVEN("unsigned long long value and string buffer") + { + constexpr unsigned long long value = 1234567890123456789ULL; + constexpr const char* expectedStr = "1234567890123456789"; + + WHEN("to_string is called") + { + NTSTATUS status = to_string(value, buffer); + + THEN("conversion succeeds") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } +} + +SCENARIO("to_wstring for numeric values") +{ + kf::wstring buffer; + + GIVEN("positive int value") + { + constexpr int value = 123; + constexpr const wchar_t* expectedStr = L"123"; + + WHEN("to_wstring is called") + { + NTSTATUS status = to_wstring(value, buffer); + + THEN("conversion succeeds") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } + + GIVEN("negative int and wstring buffer") + { + constexpr int value = -55; + constexpr const wchar_t* expectedStr = L"-55"; + + WHEN("to_wstring is called") + { + NTSTATUS status = to_wstring(value, buffer); + + THEN("conversion succeeds") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } + + GIVEN("long long value and wstring buffer") + { + constexpr long long value = 9223372036854775807LL; // LLONG_MAX + constexpr const wchar_t* expectedStr = L"9223372036854775807"; + + WHEN("to_wstring is called") + { + NTSTATUS status = kf::to_wstring(value, buffer); + + THEN("conversion succeeds and buffer contains string representation") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } + + GIVEN("unsigned long long value and wstring buffer") + { + constexpr unsigned long long value = 18446744073709551615ULL; // ULLONG_MAX + constexpr const wchar_t* expectedStr = L"18446744073709551615"; + + WHEN("to_wstring is called") + { + NTSTATUS status = kf::to_wstring(value, buffer); + + THEN("conversion succeeds and buffer contains string representation") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(buffer == expectedStr); + } + } + } +}