diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index 2b7e77b..b8ad2ab 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -2,7 +2,7 @@ #include "String/Char.h" #include "Memory/Memory.h" -#include "String/CString.h" +#include "String/StringView.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN @@ -236,168 +236,82 @@ void TestCString() { auto TestTCString = [](TInPlaceType) { - constexpr size_t BUFFER_SIZE = 64; + { + TStringView Empty; - T StrA[BUFFER_SIZE]; - T StrB[BUFFER_SIZE]; - T StrC[BUFFER_SIZE]; - T StrD[BUFFER_SIZE]; + always_check(Empty == LITERAL(T, "")); - const T* EndA = &StrA[BUFFER_SIZE]; - const T* EndB = &StrB[BUFFER_SIZE]; - const T* EndC = &StrC[BUFFER_SIZE]; - const T* EndD = &StrD[BUFFER_SIZE]; + TStringView ViewI = LITERAL(T, "#Hello, World! Goodbye, World!#"); - always_check(TCString::Copy(StrA, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr); - always_check(TCString::Copy(StrB, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr); - always_check(TCString::Copy(StrC, nullptr, LITERAL(T, "World"), nullptr) != nullptr); - always_check(TCString::Copy(StrD, nullptr, LITERAL(T, " "), nullptr) != nullptr); + ViewI.RemovePrefix(1); + ViewI.RemoveSuffix(1); - always_check(TCString::Length(StrA, &StrA[4]) == 4); - always_check(TCString::Length(StrA, EndA ) == 5); - always_check(TCString::Length(StrA, nullptr ) == 5); + T Buffer[64]; - const T* PtrA = LITERAL(T, "Hel"); - const T* PtrB = LITERAL(T, "Hello"); + Memory::Memzero(Buffer); - always_check(TCString::Compare(PtrA, nullptr, PtrB, &PtrB[3]) == 0); + ViewI.Copy(Buffer); - always_check(TCString::Compare(StrA, nullptr, StrB, nullptr) == TCString::Compare(StrA, EndA, StrB, EndB)); - always_check(TCString::Compare(StrA, nullptr, StrC, nullptr) == TCString::Compare(StrA, EndA, StrC, EndC)); - always_check(TCString::Compare(StrA, nullptr, StrC, nullptr) < 0); + TStringView ViewII = Buffer; - Memory::Memzero(StrD); + always_check(ViewI == LITERAL(T, "Hello, World! Goodbye, World!")); + always_check(ViewII == LITERAL(T, "Hello, World! Goodbye, World!")); - always_check(TCString::Compare(StrA, EndA , StrD, EndD ) > 0); - always_check(TCString::Compare(StrA, nullptr, StrD, nullptr) > 0); + TStringView ViewA(ViewI.Begin(), 13); + TStringView ViewB(ViewI.Begin(), ViewI.End()); + TStringView ViewC(&Buffer[0], 13); + TStringView ViewD(&Buffer[0]); - always_check(TCString::Copy(StrD, nullptr, StrA, nullptr) != nullptr); + always_check(ViewA == LITERAL(T, "Hello, World!")); + always_check(ViewB == LITERAL(T, "Hello, World! Goodbye, World!")); + always_check(ViewC == LITERAL(T, "Hello, World!")); + always_check(ViewD == LITERAL(T, "Hello, World! Goodbye, World!")); + } - always_check(TCString::Compare(StrA, EndA , StrD, EndD ) == 0); - always_check(TCString::Compare(StrA, nullptr, StrD, nullptr) == 0); + { + TStringView View = LITERAL(T, "Hello, World! Goodbye, World!"); - Memory::Memzero(StrC); - Memory::Memzero(StrD); + always_check( View.StartsWith(LITERAL(T, "Hello, World!"))); + always_check(!View.StartsWith(LITERAL(T, "Goodbye, World!"))); + always_check( View.StartsWith(LITERAL(T, 'H'))); + always_check(!View.StartsWith(LITERAL(T, 'G'))); + always_check(!View.EndsWith(LITERAL(T, "Hello, World!"))); + always_check( View.EndsWith(LITERAL(T, "Goodbye, World!"))); + always_check( View.EndsWith(LITERAL(T, '!'))); + always_check(!View.EndsWith(LITERAL(T, '?'))); + always_check( View.Contains(LITERAL(T, "Hello, World!"))); + always_check( View.Contains(LITERAL(T, "Goodbye, World!"))); + always_check( View.Contains(LITERAL(T, '!'))); + always_check(!View.Contains(LITERAL(T, '?'))); + } - always_check(TCString::Copy(StrD, &StrD[4], StrA, nullptr) == nullptr); + { + TStringView View = LITERAL(T, "Hello, World! Goodbye, World!"); - always_check(TCString::Compare(StrC, EndC , StrD, EndD ) == 0); - always_check(TCString::Compare(StrC, nullptr, StrD, nullptr) == 0); + always_check(View.Find(LITERAL(T, "")) == 0); + always_check(View.Find(LITERAL(T, "World")) == 7); + always_check(View.Find(LITERAL(T, 'l')) == 2); + always_check(View.RFind(LITERAL(T, "")) == 29); + always_check(View.RFind(LITERAL(T, "World")) == 23); + always_check(View.RFind(LITERAL(T, 'l')) == 26); - always_check(TCString::Copy(StrD, nullptr, StrA, &StrA[4]) != nullptr); + always_check(View.Find(LITERAL(T, ""), 13) == 13); + always_check(View.Find(LITERAL(T, "World"), 13) == 23); + always_check(View.Find(LITERAL(T, 'l'), 13) == 26); + always_check(View.RFind(LITERAL(T, ""), 13) == 13); + always_check(View.RFind(LITERAL(T, "World"), 13) == 7); + always_check(View.RFind(LITERAL(T, 'l'), 13) == 10); - always_check(TCString::Length(StrD, nullptr) == 4); + always_check(View.FindFirstOf(LITERAL(T, "eor")) == 1); + always_check(View.FindFirstOf(LITERAL(T, 'l')) == 2); + always_check(View.FindLastOf(LITERAL(T, "eor")) == 25); + always_check(View.FindLastOf(LITERAL(T, 'l')) == 26); - always_check(TCString::Compare(StrA, &StrA[4], StrD, &StrD[4]) == 0); - always_check(TCString::Compare(StrA, nullptr , StrD, nullptr ) > 0); - - const T* PtrC = LITERAL(T, "World!"); - - always_check(TCString::Copy( StrB, nullptr, PtrC, &PtrC[5]) != nullptr); - always_check(TCString::Compare(StrB, nullptr, LITERAL(T, "World"), nullptr ) == 0); - - Memory::Memzero(StrD); - - always_check(TCString::Cat(StrD, &StrD[8], StrA, nullptr) != nullptr); - always_check(TCString::Cat(StrD, &StrD[8], LITERAL(T, " "), nullptr) != nullptr); - always_check(TCString::Cat(StrD, &StrD[8], StrB, nullptr) == nullptr); - - always_check(TCString::Compare(StrD, nullptr, LITERAL(T, "Hello "), nullptr) == 0); - - Memory::Memzero(StrD); - - always_check(TCString::Cat(StrD, nullptr, StrA, nullptr) != nullptr); - always_check(TCString::Cat(StrD, nullptr, LITERAL(T, " "), nullptr) != nullptr); - always_check(TCString::Cat(StrD, nullptr, StrB, nullptr) != nullptr); - - always_check(TCString::Compare(StrD, nullptr, LITERAL(T, "Hello World"), nullptr) == 0); - - always_check(TCString::Copy(StrA, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr); - - always_check(TCString::Find(StrA, nullptr , [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); - always_check(TCString::Find(StrA, EndA , [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); - always_check(TCString::Find(StrA, nullptr , [](T A) { return A == LITERAL(T, 'o'); }) == StrA + 4); - always_check(TCString::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); }) == nullptr); - - always_check(TCString::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'o'); }) - == TCString::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); - - always_check(TCString::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'l'); }) - != TCString::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); - - always_check(TCString::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'o'); }) - == TCString::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); - - always_check(TCString::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'l'); }) - != TCString::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); - - always_check(TCString::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); }) - == TCString::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); - - always_check(TCString::Find(StrA, &StrA[3], [](T A) { return A == LITERAL(T, 'l'); }) - == TCString::Find(StrA, &StrA[3], [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, nullptr , LITERAL(T, '\0')) == StrA + 5); - always_check(TCString::FindChar(StrA, EndA , LITERAL(T, '\0')) == StrA + 5); - always_check(TCString::FindChar(StrA, nullptr , LITERAL(T, 'o')) == StrA + 4); - always_check(TCString::FindChar(StrA, &StrA[4], LITERAL(T, 'o')) == nullptr); - - always_check(TCString::FindChar(StrA, nullptr, LITERAL(T, 'o')) - == TCString::FindChar(StrA, nullptr, LITERAL(T, 'o'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, nullptr, LITERAL(T, 'l')) - != TCString::FindChar(StrA, nullptr, LITERAL(T, 'l'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, EndA, LITERAL(T, 'o')) - == TCString::FindChar(StrA, EndA, LITERAL(T, 'o'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, EndA, LITERAL(T, 'l')) - != TCString::FindChar(StrA, EndA, LITERAL(T, 'l'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, &StrA[4], LITERAL(T, 'o')) - == TCString::FindChar(StrA, &StrA[4], LITERAL(T, 'o'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, &StrA[3], LITERAL(T, 'l')) - == TCString::FindChar(StrA, &StrA[3], LITERAL(T, 'l'), ESearchDirection::FromEnd)); - - always_check(TCString::FindChar(StrA, nullptr , LITERAL(T, ""), nullptr) == nullptr); - always_check(TCString::FindChar(StrA, EndA , LITERAL(T, ""), nullptr) == nullptr); - always_check(TCString::FindChar(StrA, nullptr , LITERAL(T, "o"), nullptr) == StrA + 4); - always_check(TCString::FindChar(StrA, &StrA[4], LITERAL(T, "o"), nullptr) == nullptr); - - always_check(TCString::Copy(StrA, nullptr, LITERAL(T, "HIH"), nullptr) != nullptr); - - always_check(TCString::FindNotChar(StrA, nullptr , LITERAL(T, '\0')) == StrA); - always_check(TCString::FindNotChar(StrA, EndA , LITERAL(T, '\0')) == StrA); - always_check(TCString::FindNotChar(StrA, nullptr , LITERAL(T, 'I')) == StrA); - always_check(TCString::FindNotChar(StrA, &StrA[2], LITERAL(T, 'I')) == StrA); - - always_check(TCString::FindNotChar(StrA, nullptr , LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); - always_check(TCString::FindNotChar(StrA, EndA , LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); - always_check(TCString::FindNotChar(StrA, nullptr , LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 3); - always_check(TCString::FindNotChar(StrA, &StrA[2], LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 0); - - always_check(TCString::Copy(StrA, nullptr, LITERAL(T, "HIJIH"), nullptr) != nullptr); - - always_check(TCString::FindNotChar(StrA, nullptr, LITERAL(T, "HIJ"), nullptr) == nullptr); - always_check(TCString::FindNotChar(StrA, EndA , LITERAL(T, "HIJ"), nullptr) == nullptr); - - always_check(TCString::FindNotChar(StrA, nullptr, LITERAL(T, "H J"), nullptr) == StrA + 1); - always_check(TCString::FindNotChar(StrA, EndA , LITERAL(T, "H J"), nullptr) == StrA + 1); - - always_check(TCString::FindNotChar(StrA, nullptr, LITERAL(T, "H J"), nullptr, ESearchDirection::FromEnd) == StrA + 3); - always_check(TCString::FindNotChar(StrA, EndA , LITERAL(T, "H J"), nullptr, ESearchDirection::FromEnd) == StrA + 3); - - always_check(TCString::Copy(StrA, nullptr, LITERAL(T, "01234567890123456789"), nullptr) != nullptr); - - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, ""), nullptr) == StrA); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, ""), nullptr, ESearchDirection::FromEnd) == StrA + 20); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "345"), nullptr) == StrA + 3); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "345"), nullptr, ESearchDirection::FromEnd) == StrA + 13); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "012345678901234567890123456789"), nullptr) == nullptr); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "012345678901234567890123456789"), nullptr, ESearchDirection::FromEnd) == nullptr); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "ABC"), nullptr) == nullptr); - always_check(TCString::FindString(StrA, nullptr, LITERAL(T, "ABC"), nullptr, ESearchDirection::FromEnd) == nullptr); + always_check(View.FindFirstNotOf(LITERAL(T, "Hello! Goodbye!")) == 5); + always_check(View.FindFirstNotOf(LITERAL(T, '!')) == 0); + always_check(View.FindLastNotOf(LITERAL(T, "Hello! Goodbye!")) == 25); + always_check(View.FindLastNotOf(LITERAL(T, '!')) == 27); + } }; TestTCString(InPlaceType); diff --git a/Redcraft.Utility/Source/Public/String/CString.h b/Redcraft.Utility/Source/Public/String/CString.h deleted file mode 100644 index 72a6405..0000000 --- a/Redcraft.Utility/Source/Public/String/CString.h +++ /dev/null @@ -1,442 +0,0 @@ -#pragma once - -#include "CoreTypes.h" -#include "String/Char.h" -#include "Memory/Memory.h" -#include "Templates/Invoke.h" -#include "TypeTraits/TypeTraits.h" -#include "Miscellaneous/Compare.h" -#include "Miscellaneous/AssertionMacros.h" - -#include -#include - -NAMESPACE_REDCRAFT_BEGIN -NAMESPACE_MODULE_BEGIN(Redcraft) -NAMESPACE_MODULE_BEGIN(Utility) - -#pragma warning(push) -#pragma warning(disable : 4996) - -/** Determines search direction for string operations. */ -enum class ESearchDirection -{ - /** Search from the start, moving forward through the string. */ - FromStart, - - /** Search from the end, moving backward through the string. */ - FromEnd, -}; - -/** Set of utility functions operating on C-style null-terminated byte strings. */ -template -struct TCString -{ - using CharType = T; - - /** Copies one string to another. The end sentinel is used only for buffer safety and will not append null characters to the destination. */ - FORCEINLINE static CharType* Copy(CharType* Destination, const CharType* DestinationEnd, const CharType* Source, const CharType* SourceEnd) - { - checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr.")); - - if (DestinationEnd == nullptr && SourceEnd == nullptr) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strcpy(Destination, Source); - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcscpy(Destination, Source); - } - } - - size_t SourceLength = TCString::Length(Source, SourceEnd); - - if (DestinationEnd != nullptr && Destination + SourceLength + 1 > DestinationEnd) - { - return nullptr; - } - - Memory::Memcpy(Destination, Source, SourceLength * sizeof(CharType)); - - Destination[SourceLength] = LITERAL(CharType, '\0'); - - return Destination; - } - - /** Concatenates two strings. The end sentinel is used only for buffer safety and will not append null characters to the destination. */ - FORCEINLINE static CharType* Cat(CharType* Destination, const CharType* DestinationEnd, const CharType* Source, const CharType* SourceEnd) - { - checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr.")); - - if (DestinationEnd == nullptr && SourceEnd == nullptr) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strcat(Destination, Source); - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcscat(Destination, Source); - } - } - - size_t DestinationLength = TCString::Length(Destination, DestinationEnd); - - CharType* Result = Copy(Destination + DestinationLength, DestinationEnd, Source, SourceEnd); - - return Result ? Destination : nullptr; - } - - /** @return The length of a given string. The maximum length is the buffer size. */ - NODISCARD FORCEINLINE static size_t Length(const CharType* InString, const CharType* End) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - if (End == nullptr) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strlen(InString); - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcslen(InString); - } - } - - size_t Result = 0; - - while (*InString != LITERAL(CharType, '\0') && InString != End) - { - ++Result; - ++InString; - } - - return Result; - } - - /** Compares two strings. The end sentinel is used only for buffer safety not for comparison. */ - NODISCARD FORCEINLINE static strong_ordering Compare(const CharType* LHS, const CharType* LHSEnd, const CharType* RHS, const CharType* RHSEnd) - { - checkf(LHS && RHS, TEXT("Read access violation. LHS and RHS must not be nullptr.")); - - if (LHSEnd == nullptr && RHSEnd == nullptr) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strcmp(LHS, RHS) <=> 0; - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcscmp(LHS, RHS) <=> 0; - } - } - - while (LHS != LHSEnd && RHS != RHSEnd) - { - if (*LHS != *RHS) - { - return *LHS <=> *RHS; - } - - if (*LHS == LITERAL(CharType, '\0') && *RHS == LITERAL(CharType, '\0')) - { - return strong_ordering::equal; - } - - ++LHS; - ++RHS; - } - - if (LHS != LHSEnd && RHS == RHSEnd) - { - return *LHS <=> LITERAL(CharType, '\0'); - } - else if (LHS == LHSEnd && RHS != RHSEnd) - { - return LITERAL(CharType, '\0') <=> *RHS; - } - - return strong_ordering::equal; - } - - /** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - template F> - NODISCARD FORCEINLINE static const CharType* Find(const CharType* InString, const CharType* End, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - if (SearchDirection == ESearchDirection::FromStart) - { - while (InString != End) - { - if (InvokeResult(Forward(InPredicate), *InString)) - { - return InString; - } - - if (*InString == LITERAL(CharType, '\0')) break; - - ++InString; - } - } - else - { - size_t Index = TCString::Length(InString, End); - - const CharType* Iter = InString + Index; - - if (Iter == End) --Iter; - - while (Iter != InString - 1) - { - if (InvokeResult(Forward(InPredicate), *Iter)) - { - return Iter; - } - - --Iter; - } - } - - return nullptr; - } - - /** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - template F> - NODISCARD FORCEINLINE static CharType* Find( CharType* InString, const CharType* End, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::Find(const_cast(InString), End, Forward(InPredicate), SearchDirection)); - } - - /** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - if (End == nullptr) - { - if constexpr (CSameAs) - { - return SearchDirection == ESearchDirection::FromStart ? NAMESPACE_STD::strchr(InString, Character) : NAMESPACE_STD::strrchr(InString, Character); - } - else if constexpr (CSameAs) - { - return SearchDirection == ESearchDirection::FromStart ? NAMESPACE_STD::wcschr(InString, Character) : NAMESPACE_STD::wcsrchr(InString, Character); - } - } - - return TCString::Find(InString, End, [Character](CharType C) { return C == Character; }, SearchDirection); - } - - /** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::FindChar(const_cast(InString), End, Character, SearchDirection)); - } - - /** Finds the first or last occurrence of a character in a charset. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); - - if (End == nullptr && CharsetEnd == nullptr && SearchDirection == ESearchDirection::FromStart) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strpbrk(InString, Charset); - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcspbrk(InString, Charset); - } - } - - return TCString::Find - ( - InString, End, - [Charset, CharsetEnd](CharType C) - { - const CharType* Result = TCString::FindChar(Charset, CharsetEnd, C); - return Result != nullptr && *Result != LITERAL(CharType, '\0'); - }, - SearchDirection - ); - } - - /** Finds the first or last occurrence of a character in a charset. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::FindChar(const_cast(InString), End, Charset, CharsetEnd, SearchDirection)); - } - - /** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - if (InString == End) return nullptr; - - if (Character == LITERAL(CharType, '\0') && SearchDirection == ESearchDirection::FromStart) - { - return *InString != LITERAL(CharType, '\0') ? InString : nullptr; - } - - if (End == nullptr && SearchDirection == ESearchDirection::FromStart) - { - if constexpr (CSameAs) - { - const CharType Charset[] = { Character, LITERAL(CharType, '\0') }; - return InString + NAMESPACE_STD::strspn(InString, Charset); - } - else if constexpr (CSameAs) - { - const CharType Charset[] = { Character, LITERAL(CharType, '\0') }; - return InString + NAMESPACE_STD::wcsspn(InString, Charset); - } - } - - return TCString::Find(InString, End, [Character](CharType C) { return C != Character; }, SearchDirection); - } - - /** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::FindNotChar(const_cast(InString), End, Character, SearchDirection)); - } - - /** Finds the first or last occurrence of a character that is not in the given charset. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); - - if (End == nullptr && CharsetEnd == nullptr && SearchDirection == ESearchDirection::FromStart) - { - if constexpr (CSameAs) - { - size_t Index = NAMESPACE_STD::strspn(InString, Charset); - return InString[Index] != LITERAL(CharType, '\0') ? InString + Index : nullptr; - } - else if constexpr (CSameAs) - { - size_t Index = NAMESPACE_STD::wcsspn(InString, Charset); - return InString[Index] != LITERAL(CharType, '\0') ? InString + Index : nullptr; - } - } - - return TCString::Find(InString, End, [Charset, CharsetEnd](CharType C) { return TCString::FindChar(Charset, CharsetEnd, C) == nullptr; }, SearchDirection); - } - - /** Finds the first or last occurrence of a character that is not in the given charset. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::FindNotChar(const_cast(InString), End, Charset, CharsetEnd, SearchDirection)); - } - - /** Finds the first or last occurrence of a substring. The end sentinel is used only for buffer safety. */ - NODISCARD static const CharType* FindString(const CharType* InString, const CharType* End, const CharType* Substring, const CharType* SubstringEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr.")); - - if (InString == End) return nullptr; - - if (Substring == SubstringEnd || *Substring == LITERAL(CharType, '\0')) - { - if (SearchDirection == ESearchDirection::FromStart) return InString; - else - { - const CharType* Iter = InString + TCString::Length(InString, End); - if (Iter == End) --Iter; - return Iter; - } - } - - if (End == nullptr && SubstringEnd == nullptr && SearchDirection == ESearchDirection::FromStart) - { - if constexpr (CSameAs) - { - return NAMESPACE_STD::strstr(InString, Substring); - } - else if constexpr (CSameAs) - { - return NAMESPACE_STD::wcsstr(InString, Substring); - } - } - - size_t StringLength = TCString::Length(InString, End); - size_t SubstringLength = TCString::Length(Substring, SubstringEnd); - - if (StringLength < SubstringLength) - { - return nullptr; - } - - if (SearchDirection == ESearchDirection::FromStart) - { - for (size_t Index = 0; Index < StringLength - SubstringLength; ++Index) - { - if (TCString::Compare(InString + Index, InString + Index + SubstringLength, Substring, Substring + SubstringLength) == 0) - { - return InString + Index; - } - } - } - else - { - for (size_t Index = StringLength - SubstringLength; Index > 0; --Index) - { - if (TCString::Compare(InString + Index, InString + Index + SubstringLength, Substring, Substring + SubstringLength) == 0) - { - return InString + Index; - } - } - } - - return nullptr; - } - - /** Finds the first or last occurrence of a substring. The end sentinel is used only for buffer safety. */ - NODISCARD FORCEINLINE static CharType* FindString( CharType* InString, const CharType* End, const CharType* Substring, const CharType* SubstringEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart) - { - checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr.")); - - check_no_recursion(); - - return const_cast(TCString::FindString(const_cast(InString), End, Substring, SubstringEnd, SearchDirection)); - } - -}; - -using FCString = TCString; -using FWCString = TCString; -using FU8CString = TCString; -using FU16CString = TCString; -using FU32CString = TCString; - -#pragma warning(pop) - -NAMESPACE_MODULE_END(Utility) -NAMESPACE_MODULE_END(Redcraft) -NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/String/StringView.h b/Redcraft.Utility/Source/Public/String/StringView.h new file mode 100644 index 0000000..d1eb671 --- /dev/null +++ b/Redcraft.Utility/Source/Public/String/StringView.h @@ -0,0 +1,436 @@ +#pragma once + +#include "CoreTypes.h" +#include "String/Char.h" +#include "Memory/Allocator.h" +#include "Templates/Utility.h" +#include "Templates/TypeHash.h" +#include "Templates/Container.h" +#include "Containers/Iterator.h" +#include "Containers/ArrayView.h" +#include "TypeTraits/TypeTraits.h" +#include "Miscellaneous/Compare.h" +#include "Memory/ObserverPointer.h" +#include "Miscellaneous/AssertionMacros.h" + +#include +#include + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +/** + * The class template TStringView describes an object that can refer to a constant contiguous sequence of char-like objects + * with the first element of the sequence at position zero. Provides a set of convenient string processing functions. + */ +template +class TStringView final +{ +public: + + using ElementType = T; + + using Reference = typename TArrayView::Reference; + + using Iterator = typename TArrayView::Iterator; + using ReverseIterator = typename TArrayView::ReverseIterator; + + static_assert(CContiguousIterator); + + /** Constructs an empty string view. */ + FORCEINLINE constexpr TStringView() = default; + + /** Constructs a string view that is a view over the range ['InFirst', 'InFirst' + 'Count'). */ + template requires (CConvertibleTo(*)[], const ElementType(*)[]>) + FORCEINLINE constexpr TStringView(I InFirst, size_t InCount) : NativeData(InFirst, InCount) { } + + /** Constructs a string view that is a view over the range ['InFirst', 'InLast'). */ + template S> requires (CConvertibleTo(*)[], const ElementType(*)[]>) + FORCEINLINE constexpr TStringView(I InFirst, S InLast) : NativeData(InFirst, InLast) { } + + /** Constructs a string view that is a view over the range ['InPtr', 'InPtr' + 'Count'). */ + FORCEINLINE constexpr TStringView(const ElementType* InPtr, size_t Count) : NativeData(InPtr, Count) + { + checkf(InPtr != nullptr, TEXT("TStringView cannot be initialized by nullptr. Please check the pointer.")); + } + + FORCEINLINE constexpr TStringView(nullptr_t, size_t) = delete; + + /** Constructs a string view that is a view over the range ['InPtr', '\0'). */ + FORCEINLINE constexpr TStringView(const ElementType* InPtr) + { + checkf(InPtr != nullptr, TEXT("TStringView cannot be initialized by nullptr. Please check the pointer.")); + + size_t Length = 0; + + if constexpr (CSameAs) + { + Length = NAMESPACE_STD::strlen(InPtr); + } + else if constexpr (CSameAs) + { + Length = NAMESPACE_STD::wcslen(InPtr); + } + else + { + while (InPtr[Length] != LITERAL(ElementType, '\0')) ++Length; + } + + NativeData = TArrayView(InPtr, Length); + } + + FORCEINLINE constexpr TStringView(nullptr_t) = delete; + + /** Defaulted copy constructor copies the size and data pointer. */ + FORCEINLINE constexpr TStringView(const TStringView&) = default; + + /** Assigns other to *this. This defaulted assignment operator performs a shallow copy of the data pointer and the size. */ + FORCEINLINE constexpr TStringView& operator=(const TStringView&) noexcept = default; + + /** Compares the contents of two string views. */ + NODISCARD friend constexpr bool operator==(TStringView LHS, TStringView RHS) { return LHS.NativeData == RHS.NativeData; } + + /** Compares the contents of two string views. */ + NODISCARD friend constexpr auto operator<=>(TStringView LHS, TStringView RHS) { return LHS.NativeData <=> RHS.NativeData; } + + /** Shrinks the view by moving its start forward. */ + FORCEINLINE constexpr void RemovePrefix(size_t Count) + { + checkf(Count <= Num(), TEXT("Illegal subview range. Please check Count.")); + + NativeData = NativeData.Subview(Count); + } + + /** Shrinks the view by moving its end backward. */ + FORCEINLINE constexpr void RemoveSuffix(size_t Count) + { + checkf(Count <= Num(), TEXT("Illegal subview range. Please check Count.")); + + NativeData = NativeData.Subview(0, Num() - Count); + } + + /** Obtains a string view that is a view over the first 'Count' elements of this string view. */ + NODISCARD FORCEINLINE constexpr TStringView First(size_t Count) const + { + checkf(Count <= Num(), TEXT("Illegal subview range. Please check Count.")); + + return TStringView(Begin(), Count); + } + + /** Obtains a string view that is a view over the last 'Count' elements of this string view. */ + NODISCARD FORCEINLINE constexpr TStringView Last(size_t Count) const + { + checkf(Count <= Num(), TEXT("Illegal subview range. Please check Count.")); + + return TStringView(End() - Count, Count); + } + + /** Copies the elements of this string view to the destination buffer without null-termination. */ + FORCEINLINE constexpr size_t Copy(ElementType* Dest, size_t Count = DynamicExtent, size_t Offset = 0) const + { + checkf(Offset <= Num() && (Count == DynamicExtent || Offset + Count <= Num()), TEXT("Illegal subview range. Please check Offset and Count.")); + + if (Count == DynamicExtent) + { + Count = Num() - Offset; + } + + Memory::Memcpy(Dest, GetData().Get() + Offset, Count * sizeof(ElementType)); + + return Count; + } + + /** Obtains a string view that is a view over the 'Count' elements of this string view starting at 'Offset'. */ + NODISCARD FORCEINLINE constexpr TStringView Substr(size_t Offset, size_t Count = DynamicExtent) const + { + checkf(Offset <= Num() && (Count == DynamicExtent || Offset + Count <= Num()), TEXT("Illegal subview range. Please check Offset and Count.")); + + return Subview(Offset, Count); + } + + /** Obtains a string view that is a view over the 'Count' elements of this string view starting at 'Offset'. */ + NODISCARD FORCEINLINE constexpr TStringView Subview(size_t Offset, size_t Count = DynamicExtent) const + { + checkf(Offset <= Num() && (Count == DynamicExtent || Offset + Count <= Num()), TEXT("Illegal subview range. Please check Offset and Count.")); + + if (Count != DynamicExtent) + { + return TStringView(Begin() + Offset, Count); + } + else + { + return TStringView(Begin() + Offset, Num() - Offset); + } + } + + /** @return true if the string view starts with the given prefix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool StartsWith(TStringView Prefix) const + { + return Num() >= Prefix.Num() && Substr(0, Prefix.Num()) == Prefix; + } + + /** @return true if the string view starts with the given prefix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool StartsWith(ElementType Prefix) const + { + return Num() >= 1 && Front() == Prefix; + } + + /** @return true if the string view ends with the given suffix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool EndsWith(TStringView Suffix) const + { + return Num() >= Suffix.Num() && Substr(Num() - Suffix.Num(), Suffix.Num()) == Suffix; + } + + /** @return true if the string view ends with the given suffix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool EndsWith(ElementType Suffix) const + { + return Num() >= 1 && Back() == Suffix; + } + + /** @return true if the string view contains the given substring, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool Contains(TStringView View) const + { + return Find(View) != INDEX_NONE; + } + + /** @return true if the string view contains the given character, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool Contains(ElementType Char) const + { + return Find(Char) != INDEX_NONE; + } + + /** @return true if the string view contains character that satisfy the given predicate, false otherwise. */ + template F> + NODISCARD FORCEINLINE constexpr bool Contains(F&& InPredicate) const + { + return Find(Forward(InPredicate)) != INDEX_NONE; + } + + /** @return The index of the first occurrence of the given substring, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t Find(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + if (View.Num() > Num()) return INDEX_NONE; + + if (View.Num() == 0) return Index; + + for (; Index != Num() - View.Num() + 1; ++Index) + { + if (Substr(Index).StartsWith(View)) + { + return Index; + } + } + + return INDEX_NONE; + } + + /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t Find(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + for (; Index != Num(); ++Index) + { + if (NativeData[Index] == Char) + { + return Index; + } + } + + return INDEX_NONE; + } + + /** @return The index of the first occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ + template F> + NODISCARD FORCEINLINE constexpr size_t Find(F&& InPredicate, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + for (; Index != Num(); ++Index) + { + if (InvokeResult(Forward(InPredicate), NativeData[Index])) + { + return Index; + } + } + + return INDEX_NONE; + } + + /** @return The index of the last occurrence of the given substring, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t RFind(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + if (View.Num() > Num()) return INDEX_NONE; + + if (Index == INDEX_NONE) Index = Num(); + + if (View.Num() == 0) return Index; + + for (; Index != View.Num() - 1; --Index) + { + if (Substr(0, Index).EndsWith(View)) + { + return Index - View.Num(); + } + } + + return INDEX_NONE; + } + + /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t RFind(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + if (Index == INDEX_NONE) Index = Num(); + + for (; Index != 0; --Index) + { + if (NativeData[Index - 1] == Char) + { + return Index - 1; + } + } + + return INDEX_NONE; + } + + /** @return The index of the last occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ + template F> + NODISCARD FORCEINLINE constexpr size_t RFind(F&& InPredicate, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + if (Index == INDEX_NONE) Index = Num(); + + for (; Index != 0; --Index) + { + if (InvokeResult(Forward(InPredicate), NativeData[Index - 1])) + { + return Index - 1; + } + } + + return INDEX_NONE; + } + + /** @return The index of the first occurrence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstOf(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return Find([View](ElementType Char) { return View.Contains(Char); }, Index); + } + + /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstOf(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return Find(Char, Index); + } + + /** @return The index of the last occurrence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastOf(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return RFind([View](ElementType Char) { return View.Contains(Char); }, Index); + } + + /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastOf(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return RFind(Char, Index); + } + + /** @return The index of the first absence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstNotOf(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return Find([View](ElementType Char) { return !View.Contains(Char); }, Index); + } + + /** @return The index of the first absence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstNotOf(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return Find([Char](ElementType C) { return C != Char; }, Index); + } + + /** @return The index of the last absence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastNotOf(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return RFind([View](ElementType Char) { return !View.Contains(Char); }, Index); + } + + /** @return The index of the last absence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastNotOf(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return RFind([Char](ElementType C) { return C != Char; }, Index); + } + + /** @return The pointer to the underlying element storage. */ + NODISCARD FORCEINLINE constexpr TObserverPtr GetData() const { return NativeData.GetData(); } + + /** @return The iterator to the first or end element. */ + NODISCARD FORCEINLINE constexpr Iterator Begin() const { return NativeData.Begin(); } + NODISCARD FORCEINLINE constexpr Iterator End() const { return NativeData.End(); } + + /** @return The reverse iterator to the first or end element. */ + NODISCARD FORCEINLINE constexpr ReverseIterator RBegin() const { return NativeData.RBegin(); } + NODISCARD FORCEINLINE constexpr ReverseIterator REnd() const { return NativeData.REnd(); } + + /** @return The number of elements in the container. */ + NODISCARD FORCEINLINE constexpr size_t Num() const { return NativeData.Num(); } + + /** @return true if the container is empty, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return Num() == 0; } + + /** @return true if the iterator is valid, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool IsValidIterator(Iterator Iter) const { return Begin() <= Iter && Iter <= End(); } + + /** @return The reference to the requested element. */ + NODISCARD FORCEINLINE constexpr Reference operator[](size_t Index) const { checkf(Index < Num(), TEXT("Read access violation. Please check IsValidIterator().")); return NativeData[Index]; } + + /** @return The reference to the first or last element. */ + NODISCARD FORCEINLINE constexpr Reference Front() const { return *Begin(); } + NODISCARD FORCEINLINE constexpr Reference Back() const { return *(End() - 1); } + + /** Overloads the GetTypeHash algorithm for TStringView. */ + NODISCARD friend FORCEINLINE constexpr size_t GetTypeHash(TStringView A) { return GetTypeHash(A.NativeData); } + + ENABLE_RANGE_BASED_FOR_LOOP_SUPPORT + +private: + + TArrayView NativeData; + +}; + +template +TStringView(I, S) -> TStringView>; + +using FStringView = TStringView; +using FWStringView = TStringView; +using FU8StringView = TStringView; +using FU16StringView = TStringView; +using FU32StringView = TStringView; +using FUnicodeStringView = TStringView; + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END