feat(string): add null-terminated byte string handling functions and the corresponding testing
This commit is contained in:
parent
5210db43b3
commit
d137170ccb
@ -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 = []<typename T>(TInPlaceType<T>)
|
||||
{
|
||||
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<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Copy(StrB, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Copy(StrC, IGNORE_SIZE, LITERAL(T, "World"), IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::Length(StrA, 4) == 4);
|
||||
always_check(TCString<T>::Length(StrA, BUFFER_SIZE) == 5);
|
||||
always_check(TCString<T>::Length(StrA, IGNORE_SIZE) == 5);
|
||||
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrB, IGNORE_SIZE) == TCString<T>::Compare(StrA, BUFFER_SIZE, StrB, BUFFER_SIZE));
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) == TCString<T>::Compare(StrA, BUFFER_SIZE, StrC, BUFFER_SIZE));
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) < 0);
|
||||
|
||||
Memory::Memzero(StrD);
|
||||
|
||||
always_check(TCString<T>::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) > 0);
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0);
|
||||
|
||||
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0);
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0);
|
||||
|
||||
Memory::Memzero(StrC);
|
||||
Memory::Memzero(StrD);
|
||||
|
||||
always_check(TCString<T>::Copy(StrD, 4, StrA, IGNORE_SIZE) == nullptr);
|
||||
|
||||
always_check(TCString<T>::Compare(StrC, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0);
|
||||
always_check(TCString<T>::Compare(StrC, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0);
|
||||
|
||||
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, StrA, 4) != nullptr);
|
||||
|
||||
always_check(TCString<T>::Length(StrD, IGNORE_SIZE) == 4);
|
||||
|
||||
always_check(TCString<T>::Compare(StrA, 4, StrD, 4) == 0);
|
||||
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0);
|
||||
|
||||
always_check(TCString<T>::Copy( StrB, IGNORE_SIZE, LITERAL(T, "World!"), 5) != nullptr);
|
||||
always_check(TCString<T>::Compare(StrB, IGNORE_SIZE, LITERAL(T, "World" ), IGNORE_SIZE) == 0);
|
||||
|
||||
Memory::Memzero(StrD);
|
||||
|
||||
always_check(TCString<T>::Cat(StrD, 8, StrA, IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Cat(StrD, 8, LITERAL(T, " "), IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Cat(StrD, 8, StrB, IGNORE_SIZE) == nullptr);
|
||||
|
||||
always_check(TCString<T>::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello "), IGNORE_SIZE) == 0);
|
||||
|
||||
Memory::Memzero(StrD);
|
||||
|
||||
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr);
|
||||
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, StrB, IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello World"), IGNORE_SIZE) == 0);
|
||||
|
||||
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5);
|
||||
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5);
|
||||
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) == StrA + 4);
|
||||
always_check(TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }) == nullptr);
|
||||
|
||||
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); })
|
||||
== TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); })
|
||||
!= TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); })
|
||||
== TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); })
|
||||
!= TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); })
|
||||
== TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); })
|
||||
== TCString<T>::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA + 5);
|
||||
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA + 5);
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o')) == StrA + 4);
|
||||
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o')) == nullptr);
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o'))
|
||||
== TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l'))
|
||||
!= TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o'))
|
||||
== TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l'))
|
||||
!= TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o'))
|
||||
== TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, 3, LITERAL(T, 'l'))
|
||||
== TCString<T>::FindChar(StrA, 3, LITERAL(T, 'l'), ESearchDirection::FromEnd));
|
||||
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr);
|
||||
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr);
|
||||
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, "o"), IGNORE_SIZE) == StrA + 4);
|
||||
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, "o"), IGNORE_SIZE) == nullptr);
|
||||
|
||||
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIH"), IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA);
|
||||
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA);
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I')) == StrA);
|
||||
always_check(TCString<T>::FindNotChar(StrA, 2, LITERAL(T, 'I')) == StrA);
|
||||
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2);
|
||||
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2);
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 3);
|
||||
always_check(TCString<T>::FindNotChar(StrA, 2, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 0);
|
||||
|
||||
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIJIH"), IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr);
|
||||
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr);
|
||||
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1);
|
||||
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1);
|
||||
|
||||
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3);
|
||||
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3);
|
||||
|
||||
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "01234567890123456789"), IGNORE_SIZE) != nullptr);
|
||||
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == StrA);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 20);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE) == StrA + 3);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 13);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE) == nullptr);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE) == nullptr);
|
||||
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr);
|
||||
};
|
||||
|
||||
TestTCString(InPlaceType<char>);
|
||||
TestTCString(InPlaceType<wchar_t>);
|
||||
TestTCString(InPlaceType<char8_t>);
|
||||
TestTCString(InPlaceType<char16_t>);
|
||||
TestTCString(InPlaceType<char32_t>);
|
||||
}
|
||||
|
||||
NAMESPACE_END(Testing)
|
||||
|
||||
NAMESPACE_MODULE_END(Utility)
|
||||
|
@ -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; };
|
||||
|
447
Redcraft.Utility/Source/Public/String/CString.h
Normal file
447
Redcraft.Utility/Source/Public/String/CString.h
Normal file
@ -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 <cstring>
|
||||
#include <cwchar>
|
||||
|
||||
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 <CCharType T>
|
||||
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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strcpy(Destination, Source);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strcat(Destination, Source);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strlen(InString);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strcmp(LHS, RHS) <=> 0;
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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 <CPredicate<CharType> 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<bool>(Forward<F>(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<bool>(Forward<F>(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 <CPredicate<CharType> 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<CharType*>(TCString::Find(const_cast<const CharType*>(InString), BufferSize, Forward<F>(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<CharType, char>)
|
||||
{
|
||||
return SearchDirection == ESearchDirection::FromStart ? NAMESPACE_STD::strchr(InString, Character) : NAMESPACE_STD::strrchr(InString, Character);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType*>(TCString::FindChar(const_cast<const CharType*>(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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strpbrk(InString, Charset);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType*>(TCString::FindChar(const_cast<const CharType*>(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<CharType, char>)
|
||||
{
|
||||
const CharType Charset[] = { Character, LITERAL(CharType, '\0') };
|
||||
return InString + NAMESPACE_STD::strspn(InString, Charset);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(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<CharType, char>)
|
||||
{
|
||||
size_t Index = NAMESPACE_STD::strspn(InString, Charset);
|
||||
return InString[Index] != LITERAL(CharType, '\0') ? InString + Index : nullptr;
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(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<CharType, char>)
|
||||
{
|
||||
return NAMESPACE_STD::strstr(InString, Substring);
|
||||
}
|
||||
else if constexpr (CSameAs<CharType, wchar_t>)
|
||||
{
|
||||
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<CharType*>(TCString::FindString(const_cast<const CharType*>(InString), BufferSize, Substring, SubstringSize, SearchDirection));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
using FCString = TCString<char>;
|
||||
using FWCString = TCString<wchar_t>;
|
||||
using FU8CString = TCString<char8_t>;
|
||||
using FU16CString = TCString<char16_t>;
|
||||
using FU32CString = TCString<char32_t>;
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
NAMESPACE_MODULE_END(Utility)
|
||||
NAMESPACE_MODULE_END(Redcraft)
|
||||
NAMESPACE_REDCRAFT_END
|
@ -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<CharType>::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 <CCharType T>
|
||||
struct TChar
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ NAMESPACE_BEGIN(Testing)
|
||||
|
||||
REDCRAFTUTILITY_API void TestString();
|
||||
REDCRAFTUTILITY_API void TestChar();
|
||||
REDCRAFTUTILITY_API void TestCString();
|
||||
|
||||
NAMESPACE_END(Testing)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user