From d137170ccb5a52b564ca8983bd87cd09dd5740a2 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Fri, 20 Sep 2024 22:53:15 +0800 Subject: [PATCH] feat(string): add null-terminated byte string handling functions and the corresponding testing --- .../Source/Private/Testing/StringTesting.cpp | 230 +++++++-- .../Public/Miscellaneous/CoreMiscDefines.h | 2 +- .../Source/Public/String/CString.h | 447 ++++++++++++++++++ Redcraft.Utility/Source/Public/String/Char.h | 2 +- .../Source/Public/Testing/StringTesting.h | 1 + 5 files changed, 648 insertions(+), 34 deletions(-) create mode 100644 Redcraft.Utility/Source/Public/String/CString.h diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index a30fb1c..f1a496d 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -1,6 +1,8 @@ #include "Testing/StringTesting.h" #include "String/Char.h" +#include "Memory/Memory.h" +#include "String/CString.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN @@ -12,6 +14,7 @@ NAMESPACE_BEGIN(Testing) void TestString() { TestChar(); + TestCString(); } void TestChar() @@ -68,31 +71,31 @@ void TestChar() } { - //always_check(FU16Char::IsAlnum(U16TEXT('0'))); - //always_check(FU16Char::IsAlpha(U16TEXT('A'))); - //always_check(FU16Char::IsLower(U16TEXT('a'))); - //always_check(FU16Char::IsUpper(U16TEXT('A'))); +// always_check(FU16Char::IsAlnum(U16TEXT('0'))); +// always_check(FU16Char::IsAlpha(U16TEXT('A'))); +// always_check(FU16Char::IsLower(U16TEXT('a'))); +// always_check(FU16Char::IsUpper(U16TEXT('A'))); always_check(FU16Char::IsDigit(U16TEXT('0'))); always_check(FU16Char::IsCntrl(U16TEXT('\n'))); - //always_check(FU16Char::IsGraph(U16TEXT('!'))); +// always_check(FU16Char::IsGraph(U16TEXT('!'))); always_check(FU16Char::IsSpace(U16TEXT('\t'))); always_check(FU16Char::IsBlank(U16TEXT(' '))); - //always_check(FU16Char::IsPrint(U16TEXT('#'))); - //always_check(FU16Char::IsPunct(U16TEXT('['))); +// always_check(FU16Char::IsPrint(U16TEXT('#'))); +// always_check(FU16Char::IsPunct(U16TEXT('['))); } { - //always_check(FU32Char::IsAlnum(U32TEXT('0'))); - //always_check(FU32Char::IsAlpha(U32TEXT('A'))); - //always_check(FU32Char::IsLower(U32TEXT('a'))); - //always_check(FU32Char::IsUpper(U32TEXT('A'))); +// always_check(FU32Char::IsAlnum(U32TEXT('0'))); +// always_check(FU32Char::IsAlpha(U32TEXT('A'))); +// always_check(FU32Char::IsLower(U32TEXT('a'))); +// always_check(FU32Char::IsUpper(U32TEXT('A'))); always_check(FU32Char::IsDigit(U32TEXT('0'))); always_check(FU32Char::IsCntrl(U32TEXT('\n'))); - //always_check(FU32Char::IsGraph(U32TEXT('!'))); +// always_check(FU32Char::IsGraph(U32TEXT('!'))); always_check(FU32Char::IsSpace(U32TEXT('\t'))); always_check(FU32Char::IsBlank(U32TEXT(' '))); - //always_check(FU32Char::IsPrint(U32TEXT('#'))); - //always_check(FU32Char::IsPunct(U32TEXT('['))); +// always_check(FU32Char::IsPrint(U32TEXT('#'))); +// always_check(FU32Char::IsPunct(U32TEXT('['))); } { @@ -138,31 +141,31 @@ void TestChar() } { - //always_check(!FU16Char::IsAlnum(U16TEXT('$'))); - //always_check(!FU16Char::IsAlpha(U16TEXT('0'))); - //always_check(!FU16Char::IsLower(U16TEXT('A'))); - //always_check(!FU16Char::IsUpper(U16TEXT('a'))); +// always_check(!FU16Char::IsAlnum(U16TEXT('$'))); +// always_check(!FU16Char::IsAlpha(U16TEXT('0'))); +// always_check(!FU16Char::IsLower(U16TEXT('A'))); +// always_check(!FU16Char::IsUpper(U16TEXT('a'))); always_check(!FU16Char::IsDigit(U16TEXT('I'))); always_check(!FU16Char::IsCntrl(U16TEXT('_'))); - //always_check(!FU16Char::IsGraph(U16TEXT(' '))); +// always_check(!FU16Char::IsGraph(U16TEXT(' '))); always_check(!FU16Char::IsSpace(U16TEXT('='))); always_check(!FU16Char::IsBlank(U16TEXT('\r'))); - //always_check(!FU16Char::IsPrint(U16TEXT('\n'))); - //always_check(!FU16Char::IsPunct(U16TEXT('H'))); +// always_check(!FU16Char::IsPrint(U16TEXT('\n'))); +// always_check(!FU16Char::IsPunct(U16TEXT('H'))); } { - //always_check(!FU32Char::IsAlnum(U32TEXT('$'))); - //always_check(!FU32Char::IsAlpha(U32TEXT('0'))); - //always_check(!FU32Char::IsLower(U32TEXT('A'))); - //always_check(!FU32Char::IsUpper(U32TEXT('a'))); +// always_check(!FU32Char::IsAlnum(U32TEXT('$'))); +// always_check(!FU32Char::IsAlpha(U32TEXT('0'))); +// always_check(!FU32Char::IsLower(U32TEXT('A'))); +// always_check(!FU32Char::IsUpper(U32TEXT('a'))); always_check(!FU32Char::IsDigit(U32TEXT('I'))); always_check(!FU32Char::IsCntrl(U32TEXT('_'))); - //always_check(!FU32Char::IsGraph(U32TEXT(' '))); +// always_check(!FU32Char::IsGraph(U32TEXT(' '))); always_check(!FU32Char::IsSpace(U32TEXT('='))); always_check(!FU32Char::IsBlank(U32TEXT('\r'))); - //always_check(!FU32Char::IsPrint(U32TEXT('\n'))); - //always_check(!FU32Char::IsPunct(U32TEXT('H'))); +// always_check(!FU32Char::IsPrint(U32TEXT('\n'))); +// always_check(!FU32Char::IsPunct(U32TEXT('H'))); } { @@ -185,10 +188,10 @@ void TestChar() always_check(FWChar::ToLower(WTEXT('i')) == WTEXT('i')); always_check(FU8Char::ToLower(U8TEXT('i')) == U8TEXT('i')); always_check(FU8Char::ToUpper(U8TEXT('l')) == U8TEXT('L')); - //always_check(FU16Char::ToLower(U16TEXT('i')) == U16TEXT('i')); - //always_check(FU16Char::ToUpper(U16TEXT('l')) == U16TEXT('L')); - //always_check(FU32Char::ToLower(U32TEXT('i')) == U32TEXT('i')); - //always_check(FU32Char::ToUpper(U32TEXT('l')) == U32TEXT('L')); +// always_check(FU16Char::ToLower(U16TEXT('i')) == U16TEXT('i')); +// always_check(FU16Char::ToUpper(U16TEXT('l')) == U16TEXT('L')); +// always_check(FU32Char::ToLower(U32TEXT('i')) == U32TEXT('i')); +// always_check(FU32Char::ToUpper(U32TEXT('l')) == U32TEXT('L')); } { @@ -228,6 +231,169 @@ void TestChar() } } +void TestCString() +{ + auto TestTCString = [](TInPlaceType) + { + constexpr size_t BUFFER_SIZE = 64; + + T StrA[BUFFER_SIZE]; + T StrB[BUFFER_SIZE]; + T StrC[BUFFER_SIZE]; + T StrD[BUFFER_SIZE]; + + always_check(TCString::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); + always_check(TCString::Copy(StrB, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); + always_check(TCString::Copy(StrC, IGNORE_SIZE, LITERAL(T, "World"), IGNORE_SIZE) != nullptr); + always_check(TCString::Copy(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr); + + always_check(TCString::Length(StrA, 4) == 4); + always_check(TCString::Length(StrA, BUFFER_SIZE) == 5); + always_check(TCString::Length(StrA, IGNORE_SIZE) == 5); + + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrB, IGNORE_SIZE) == TCString::Compare(StrA, BUFFER_SIZE, StrB, BUFFER_SIZE)); + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) == TCString::Compare(StrA, BUFFER_SIZE, StrC, BUFFER_SIZE)); + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) < 0); + + Memory::Memzero(StrD); + + always_check(TCString::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) > 0); + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0); + + always_check(TCString::Copy(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr); + + always_check(TCString::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0); + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0); + + Memory::Memzero(StrC); + Memory::Memzero(StrD); + + always_check(TCString::Copy(StrD, 4, StrA, IGNORE_SIZE) == nullptr); + + always_check(TCString::Compare(StrC, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0); + always_check(TCString::Compare(StrC, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0); + + always_check(TCString::Copy(StrD, IGNORE_SIZE, StrA, 4) != nullptr); + + always_check(TCString::Length(StrD, IGNORE_SIZE) == 4); + + always_check(TCString::Compare(StrA, 4, StrD, 4) == 0); + always_check(TCString::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0); + + always_check(TCString::Copy( StrB, IGNORE_SIZE, LITERAL(T, "World!"), 5) != nullptr); + always_check(TCString::Compare(StrB, IGNORE_SIZE, LITERAL(T, "World" ), IGNORE_SIZE) == 0); + + Memory::Memzero(StrD); + + always_check(TCString::Cat(StrD, 8, StrA, IGNORE_SIZE) != nullptr); + always_check(TCString::Cat(StrD, 8, LITERAL(T, " "), IGNORE_SIZE) != nullptr); + always_check(TCString::Cat(StrD, 8, StrB, IGNORE_SIZE) == nullptr); + + always_check(TCString::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello "), IGNORE_SIZE) == 0); + + Memory::Memzero(StrD); + + always_check(TCString::Cat(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr); + always_check(TCString::Cat(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr); + always_check(TCString::Cat(StrD, IGNORE_SIZE, StrB, IGNORE_SIZE) != nullptr); + + always_check(TCString::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello World"), IGNORE_SIZE) == 0); + + always_check(TCString::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); + + always_check(TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); + always_check(TCString::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); + always_check(TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) == StrA + 4); + always_check(TCString::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }) == nullptr); + + always_check(TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) + == TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); + + always_check(TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); }) + != TCString::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); + + always_check(TCString::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) + == TCString::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); + + always_check(TCString::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); }) + != TCString::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); + + always_check(TCString::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }) + == TCString::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); + + always_check(TCString::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); }) + == TCString::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA + 5); + always_check(TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA + 5); + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o')) == StrA + 4); + always_check(TCString::FindChar(StrA, 4, LITERAL(T, 'o')) == nullptr); + + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o')) + == TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l')) + != TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o')) + == TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l')) + != TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, 4, LITERAL(T, 'o')) + == TCString::FindChar(StrA, 4, LITERAL(T, 'o'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, 3, LITERAL(T, 'l')) + == TCString::FindChar(StrA, 3, LITERAL(T, 'l'), ESearchDirection::FromEnd)); + + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr); + always_check(TCString::FindChar(StrA, BUFFER_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr); + always_check(TCString::FindChar(StrA, IGNORE_SIZE, LITERAL(T, "o"), IGNORE_SIZE) == StrA + 4); + always_check(TCString::FindChar(StrA, 4, LITERAL(T, "o"), IGNORE_SIZE) == nullptr); + + always_check(TCString::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIH"), IGNORE_SIZE) != nullptr); + + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA); + always_check(TCString::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA); + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I')) == StrA); + always_check(TCString::FindNotChar(StrA, 2, LITERAL(T, 'I')) == StrA); + + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); + always_check(TCString::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 3); + always_check(TCString::FindNotChar(StrA, 2, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 0); + + always_check(TCString::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIJIH"), IGNORE_SIZE) != nullptr); + + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr); + always_check(TCString::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr); + + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1); + always_check(TCString::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1); + + always_check(TCString::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3); + always_check(TCString::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3); + + always_check(TCString::Copy(StrA, IGNORE_SIZE, LITERAL(T, "01234567890123456789"), IGNORE_SIZE) != nullptr); + + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == StrA); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 20); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE) == StrA + 3); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 13); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE) == nullptr); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE) == nullptr); + always_check(TCString::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr); + }; + + TestTCString(InPlaceType); + TestTCString(InPlaceType); + TestTCString(InPlaceType); + TestTCString(InPlaceType); + TestTCString(InPlaceType); +} + NAMESPACE_END(Testing) NAMESPACE_MODULE_END(Utility) diff --git a/Redcraft.Utility/Source/Public/Miscellaneous/CoreMiscDefines.h b/Redcraft.Utility/Source/Public/Miscellaneous/CoreMiscDefines.h index b69030c..0f01718 100644 --- a/Redcraft.Utility/Source/Public/Miscellaneous/CoreMiscDefines.h +++ b/Redcraft.Utility/Source/Public/Miscellaneous/CoreMiscDefines.h @@ -19,7 +19,7 @@ NAMESPACE_MODULE_BEGIN(Utility) #define UNLIKELY [[unlikely]] #define NO_UNIQUE_ADDRESS [[no_unique_address]] -constexpr size_t INDEX_NONE = -1; +constexpr size_t INDEX_NONE = -1; struct FForceInit { explicit FForceInit() = default; }; struct FNoInit { explicit FNoInit() = default; }; diff --git a/Redcraft.Utility/Source/Public/String/CString.h b/Redcraft.Utility/Source/Public/String/CString.h new file mode 100644 index 0000000..149364d --- /dev/null +++ b/Redcraft.Utility/Source/Public/String/CString.h @@ -0,0 +1,447 @@ +#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) + +/** Explicit instructions to ignore buffer size, but may lead to buffer overflow attacks. */ +constexpr size_t IGNORE_SIZE = -1; + +/** 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 size is used only for buffer safety and will not append null characters to the destination. */ + FORCEINLINE static CharType* Copy(CharType* Destination, size_t DestinationSize, const CharType* Source, size_t SourceSize) + { + checkf(Destination && Source, "Read access violation. Destination and source must not be nullptr."); + + checkf(DestinationSize != 0 && SourceSize != 0, "Illegal buffer size. DestinationSize and SourceSize must not be zero."); + + if (DestinationSize == IGNORE_SIZE && SourceSize == IGNORE_SIZE) + { + 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, SourceSize); + + if (DestinationSize != IGNORE_SIZE && DestinationSize < SourceLength + 1) + { + return nullptr; + } + + Memory::Memcpy(Destination, Source, SourceLength * sizeof(CharType)); + + Destination[SourceLength] = LITERAL(CharType, '\0'); + + return Destination; + } + + /** Concatenates two strings. The size is used only for buffer safety and will not append null characters to the destination. */ + FORCEINLINE static CharType* Cat(CharType* Destination, size_t DestinationSize, const CharType* Source, size_t SourceSize) + { + checkf(Destination && Source, "Read access violation. Destination and source must not be nullptr."); + + checkf(DestinationSize != 0 && SourceSize != 0, "Illegal buffer size. DestinationSize and SourceSize must not be zero."); + + if (DestinationSize == IGNORE_SIZE && SourceSize == IGNORE_SIZE) + { + 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, DestinationSize); + + CharType* Result = Copy(Destination + DestinationLength, DestinationSize - DestinationLength, Source, SourceSize); + + return Result ? Destination : nullptr; + } + + /** @return The length of a given string. The maximum length is the buffer size. */ + FORCEINLINE static size_t Length(const CharType* InString, size_t SourceSize) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(SourceSize != 0, "Illegal buffer size. SourceSize must not be zero."); + + if (SourceSize == IGNORE_SIZE) + { + 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') && SourceSize--) + { + ++Result; + } + + return Result; + } + + /** Compares two strings. The size is used only for buffer safety not for comparison. */ + FORCEINLINE static strong_ordering Compare(const CharType* LHS, size_t LHSSize, const CharType* RHS, size_t RHSSize) + { + checkf(LHS && RHS, "Read access violation. LHS and RHS must not be nullptr."); + + checkf(LHSSize != 0 && RHSSize != 0, "Illegal buffer size. LHSSize and RHSSize must not be zero."); + + if (LHSSize == IGNORE_SIZE && RHSSize == IGNORE_SIZE) + { + if constexpr (CSameAs) + { + return NAMESPACE_STD::strcmp(LHS, RHS) <=> 0; + } + else if constexpr (CSameAs) + { + return NAMESPACE_STD::wcscmp(LHS, RHS) <=> 0; + } + } + + while (LHSSize-- && RHSSize--) + { + if (*LHS != *RHS) + { + return *LHS <=> *RHS; + } + + if (*LHS++ == LITERAL(CharType, '\0') || *RHS++ == LITERAL(CharType, '\0')) break; + } + + 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 size is used only for buffer safety. */ + template F> + FORCEINLINE static const CharType* Find(const CharType* InString, size_t BufferSize, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + if (SearchDirection == ESearchDirection::FromStart) + { + while (BufferSize--) + { + if (InvokeResult(Forward(InPredicate), *InString)) + { + return InString; + } + + if (*InString++ == LITERAL(CharType, '\0')) break; + } + } + else + { + size_t Index = TCString::Length(InString, BufferSize); + + if (Index == BufferSize) --Index; + + while (true) + { + if (InvokeResult(Forward(InPredicate), InString[Index])) + { + return InString + Index; + } + + if (!Index--) break; + } + } + + 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 size is used only for buffer safety. */ + template F> + FORCEINLINE static CharType* Find( CharType* InString, size_t BufferSize, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::Find(const_cast(InString), BufferSize, 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 size is used only for buffer safety. */ + FORCEINLINE static const CharType* FindChar(const CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + if (BufferSize == IGNORE_SIZE) + { + 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, BufferSize, [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 size is used only for buffer safety. */ + FORCEINLINE static CharType* FindChar( CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::FindChar(const_cast(InString), BufferSize, Character, SearchDirection)); + } + + /** Finds the first or last occurrence of a character in a charset. The size is used only for buffer safety. */ + FORCEINLINE static const CharType* FindChar(const CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Charset, "Read access violation. InString and Charset must not be nullptr."); + + checkf(BufferSize != 0 && CharsetSize != 0, "Illegal buffer size. BufferSize and CharsetSize must not be zero."); + + if (BufferSize == IGNORE_SIZE && CharsetSize == IGNORE_SIZE && 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, BufferSize, + [Charset, CharsetSize](CharType C) + { + const CharType* Result = TCString::FindChar(Charset, CharsetSize, C); + return Result != nullptr && *Result != LITERAL(CharType, '\0'); + }, + SearchDirection + ); + } + + /** Finds the first or last occurrence of a character in a charset. The size is used only for buffer safety. */ + FORCEINLINE static CharType* FindChar( CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Charset, "Read access violation. InString and Charset must not be nullptr."); + + checkf(BufferSize != 0 && CharsetSize != 0, "Illegal buffer size. BufferSize and CharsetSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::FindChar(const_cast(InString), BufferSize, Charset, CharsetSize, 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 size is used only for buffer safety. */ + FORCEINLINE static const CharType* FindNotChar(const CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + if (Character == LITERAL(CharType, '\0') && SearchDirection == ESearchDirection::FromStart) + { + return *InString != LITERAL(CharType, '\0') ? InString : nullptr; + } + + if (BufferSize == IGNORE_SIZE && 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, BufferSize, [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 size is used only for buffer safety. */ + FORCEINLINE static CharType* FindNotChar( CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString, "Read access violation. InString must not be nullptr."); + + checkf(BufferSize != 0, "Illegal buffer size. BufferSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::FindNotChar(const_cast(InString), BufferSize, Character, SearchDirection)); + } + + /** Finds the first or last occurrence of a character that is not in the given charset. The size is used only for buffer safety. */ + FORCEINLINE static const CharType* FindNotChar(const CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Charset, "Read access violation. InString and Charset must not be nullptr."); + + checkf(BufferSize != 0 && CharsetSize != 0, "Illegal buffer size. BufferSize and CharsetSize must not be zero."); + + if (BufferSize == IGNORE_SIZE && CharsetSize == IGNORE_SIZE && 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, BufferSize, [Charset, CharsetSize](CharType C) { return TCString::FindChar(Charset, CharsetSize, C) == nullptr; }, SearchDirection); + } + + /** Finds the first or last occurrence of a character that is not in the given charset. The size is used only for buffer safety. */ + FORCEINLINE static CharType* FindNotChar( CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Charset, "Read access violation. InString and Charset must not be nullptr."); + + checkf(BufferSize != 0 && CharsetSize != 0, "Illegal buffer size. BufferSize and CharsetSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::FindNotChar(const_cast(InString), BufferSize, Charset, CharsetSize, SearchDirection)); + } + + /** Finds the first or last occurrence of a substring. The size is used only for buffer safety. */ + FORCEINLINE static const CharType* FindString(const CharType* InString, size_t BufferSize, const CharType* Substring, size_t SubstringSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Substring, "Read access violation. InString and Substring must not be nullptr."); + + checkf(BufferSize != 0 && SubstringSize != 0, "Illegal buffer size. BufferSize and SubstringSize must not be zero."); + + if (*Substring == LITERAL(CharType, '\0')) + { + return SearchDirection == ESearchDirection::FromStart ? InString : InString + TCString::Length(InString, BufferSize); + } + + if (BufferSize == IGNORE_SIZE && SubstringSize == IGNORE_SIZE && 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, BufferSize); + size_t SubstringLength = TCString::Length(Substring, SubstringSize); + + if (StringLength < SubstringLength) + { + return nullptr; + } + + if (SearchDirection == ESearchDirection::FromStart) + { + for (size_t Index = 0; Index < StringLength - SubstringLength; ++Index) + { + if (TCString::Compare(InString + Index, SubstringLength, Substring, SubstringLength) == 0) + { + return InString + Index; + } + } + } + else + { + for (size_t Index = StringLength - SubstringLength; Index > 0; --Index) + { + if (TCString::Compare(InString + Index, SubstringLength, Substring, SubstringLength) == 0) + { + return InString + Index; + } + } + } + + return nullptr; + } + + /** Finds the first or last occurrence of a substring. The size is used only for buffer safety. */ + FORCEINLINE static CharType* FindString( CharType* InString, size_t BufferSize, const CharType* Substring, size_t SubstringSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) + { + checkf(InString && Substring, "Read access violation. InString and Substring must not be nullptr."); + + checkf(BufferSize != 0 && SubstringSize != 0, "Illegal buffer size. BufferSize and SubstringSize must not be zero."); + + check_no_recursion(); + + return const_cast(TCString::FindString(const_cast(InString), BufferSize, Substring, SubstringSize, 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/Char.h b/Redcraft.Utility/Source/Public/String/Char.h index adcdc2d..63a5bdc 100644 --- a/Redcraft.Utility/Source/Public/String/Char.h +++ b/Redcraft.Utility/Source/Public/String/Char.h @@ -59,7 +59,7 @@ NAMESPACE_PRIVATE_END /** Templated literal struct to allow selection of string literals based on the character type provided, and not on compiler switches. */ #define LITERAL(CharType, StringLiteral) NAMESPACE_PRIVATE::TLiteral::Select(StringLiteral, WTEXT(StringLiteral), U8TEXT(StringLiteral), U16TEXT(StringLiteral), U32TEXT(StringLiteral)) -/** Set of utility functions operating on a single character. Implemented based on ISO 30112 "i18n" */ +/** Set of utility functions operating on a single character. Implemented based on ISO 30112 "i18n". */ template struct TChar { diff --git a/Redcraft.Utility/Source/Public/Testing/StringTesting.h b/Redcraft.Utility/Source/Public/Testing/StringTesting.h index 2370251..db7205f 100644 --- a/Redcraft.Utility/Source/Public/Testing/StringTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/StringTesting.h @@ -10,6 +10,7 @@ NAMESPACE_BEGIN(Testing) REDCRAFTUTILITY_API void TestString(); REDCRAFTUTILITY_API void TestChar(); +REDCRAFTUTILITY_API void TestCString(); NAMESPACE_END(Testing)