mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 14:25:37 +00:00
1101 lines
31 KiB
C++
1101 lines
31 KiB
C++
#include "string.h"
|
|
#include "assert.h"
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#ifdef _MSC_VER
|
|
#define CASE_COMPARE _stricmp
|
|
#define CASE_N_COMPARE _strnicmp
|
|
#else
|
|
#define CASE_COMPARE strcasecmp
|
|
#define CASE_N_COMPARE strncasecmp
|
|
#endif
|
|
|
|
// globals
|
|
const String::StringData String::s_EmptyStringData = {const_cast<char*>(""), 0, 1, -1, true};
|
|
const String EmptyString;
|
|
|
|
// helper functions
|
|
static String::StringData* StringDataAllocate(u32 allocSize)
|
|
{
|
|
DebugAssert(allocSize > 0);
|
|
|
|
String::StringData* pStringData =
|
|
reinterpret_cast<String::StringData*>(std::malloc(sizeof(String::StringData) + allocSize));
|
|
pStringData->pBuffer = reinterpret_cast<char*>(pStringData + 1);
|
|
pStringData->StringLength = 0;
|
|
pStringData->BufferSize = allocSize;
|
|
pStringData->ReadOnly = false;
|
|
pStringData->ReferenceCount = 1;
|
|
|
|
// if in debug build, set all to zero, otherwise only the first to zero.
|
|
#ifdef _DEBUG
|
|
std::memset(pStringData->pBuffer, 0, allocSize);
|
|
#else
|
|
pStringData->pBuffer[0] = 0;
|
|
if (allocSize > 1)
|
|
pStringData->pBuffer[allocSize - 1] = 0;
|
|
#endif
|
|
|
|
return pStringData;
|
|
}
|
|
|
|
static inline void StringDataAddRef(String::StringData* pStringData)
|
|
{
|
|
DebugAssert(pStringData->ReferenceCount > 0);
|
|
++pStringData->ReferenceCount;
|
|
}
|
|
|
|
static inline void StringDataRelease(String::StringData* pStringData)
|
|
{
|
|
if (pStringData->ReferenceCount == -1)
|
|
return;
|
|
|
|
DebugAssert(pStringData->ReferenceCount > 0);
|
|
u32 newRefCount = --pStringData->ReferenceCount;
|
|
if (!newRefCount)
|
|
std::free(pStringData);
|
|
}
|
|
|
|
static String::StringData* StringDataClone(const String::StringData* pStringData, u32 newSize, bool copyPastString)
|
|
{
|
|
DebugAssert(newSize >= 0);
|
|
|
|
String::StringData* pClone = StringDataAllocate(newSize);
|
|
if (pStringData->StringLength > 0)
|
|
{
|
|
u32 copyLength;
|
|
|
|
if (copyPastString)
|
|
{
|
|
copyLength = std::min(newSize, pStringData->BufferSize);
|
|
if (copyLength > 0)
|
|
{
|
|
std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength);
|
|
if (copyLength < pStringData->BufferSize)
|
|
pClone->pBuffer[copyLength - 1] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
copyLength = std::min(newSize, pStringData->StringLength);
|
|
if (copyLength > 0)
|
|
{
|
|
std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength);
|
|
pClone->pBuffer[copyLength] = 0;
|
|
}
|
|
}
|
|
|
|
pClone->StringLength = copyLength;
|
|
}
|
|
|
|
return pClone;
|
|
}
|
|
|
|
static String::StringData* StringDataReallocate(String::StringData* pStringData, u32 newSize)
|
|
{
|
|
DebugAssert(newSize > pStringData->StringLength);
|
|
DebugAssert(pStringData->ReferenceCount == 1);
|
|
|
|
// perform realloc
|
|
pStringData = reinterpret_cast<String::StringData*>(std::realloc(pStringData, sizeof(String::StringData) + newSize));
|
|
pStringData->pBuffer = reinterpret_cast<char*>(pStringData + 1);
|
|
|
|
// zero bytes in debug
|
|
#ifdef _DEBUG
|
|
if (newSize > pStringData->BufferSize)
|
|
{
|
|
u32 bytesToZero = newSize - pStringData->BufferSize;
|
|
std::memset(pStringData->pBuffer + (newSize - bytesToZero), 0, bytesToZero);
|
|
}
|
|
#else
|
|
if (newSize > pStringData->BufferSize)
|
|
{
|
|
pStringData->pBuffer[newSize - 1] = 0;
|
|
}
|
|
#endif
|
|
|
|
// update size
|
|
pStringData->BufferSize = newSize;
|
|
return pStringData;
|
|
}
|
|
|
|
static bool StringDataIsSharable(const String::StringData* pStringData)
|
|
{
|
|
return pStringData->ReadOnly || pStringData->ReferenceCount != -1;
|
|
}
|
|
|
|
static bool StringDataIsShared(const String::StringData* pStringData)
|
|
{
|
|
return pStringData->ReferenceCount > 1;
|
|
}
|
|
|
|
String::String() : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData)) {}
|
|
|
|
String::String(const String& copyString)
|
|
{
|
|
// special case: empty strings
|
|
if (copyString.IsEmpty())
|
|
{
|
|
m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
|
|
}
|
|
// is the string data sharable?
|
|
else if (StringDataIsSharable(copyString.m_pStringData))
|
|
{
|
|
m_pStringData = copyString.m_pStringData;
|
|
if (!m_pStringData->ReadOnly)
|
|
StringDataAddRef(m_pStringData);
|
|
}
|
|
// create a clone for ourselves
|
|
else
|
|
{
|
|
// since we're going to the effort of creating a clone, we might as well create it as the smallest size possible
|
|
m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false);
|
|
}
|
|
}
|
|
|
|
String::String(const char* Text) : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData))
|
|
{
|
|
Assign(Text);
|
|
}
|
|
|
|
String::String(const char* Text, u32 Count) : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData))
|
|
{
|
|
AppendString(Text, Count);
|
|
}
|
|
|
|
String::String(String&& moveString)
|
|
{
|
|
Assign(moveString);
|
|
}
|
|
|
|
String::String(const std::string_view& sv)
|
|
{
|
|
AppendString(sv.data(), static_cast<u32>(sv.size()));
|
|
}
|
|
|
|
String::~String()
|
|
{
|
|
StringDataRelease(m_pStringData);
|
|
}
|
|
|
|
void String::EnsureOwnWritableCopy()
|
|
{
|
|
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
|
|
{
|
|
StringData* pNewStringData = StringDataClone(m_pStringData, m_pStringData->StringLength + 1, false);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
}
|
|
}
|
|
|
|
void String::EnsureRemainingSpace(u32 spaceRequired)
|
|
{
|
|
StringData* pNewStringData;
|
|
u32 requiredReserve = m_pStringData->StringLength + spaceRequired + 1;
|
|
|
|
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
|
|
{
|
|
pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, m_pStringData->BufferSize), false);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
}
|
|
else if (m_pStringData->BufferSize < requiredReserve)
|
|
{
|
|
u32 newSize = std::max(requiredReserve, m_pStringData->BufferSize * 2);
|
|
|
|
// if we are the only owner of the buffer, we can simply realloc it
|
|
if (m_pStringData->ReferenceCount == 1)
|
|
{
|
|
// do realloc and update pointer
|
|
m_pStringData = StringDataReallocate(m_pStringData, newSize);
|
|
}
|
|
else
|
|
{
|
|
// clone and release old
|
|
pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, newSize), false);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
}
|
|
}
|
|
}
|
|
|
|
void String::InternalAppend(const char* pString, u32 Length)
|
|
{
|
|
EnsureRemainingSpace(Length);
|
|
|
|
DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize);
|
|
DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly);
|
|
|
|
std::memcpy(m_pStringData->pBuffer + m_pStringData->StringLength, pString, Length);
|
|
m_pStringData->StringLength += Length;
|
|
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
|
|
}
|
|
|
|
void String::InternalPrepend(const char* pString, u32 Length)
|
|
{
|
|
EnsureRemainingSpace(Length);
|
|
|
|
DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize);
|
|
DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly);
|
|
|
|
std::memmove(m_pStringData->pBuffer + Length, m_pStringData->pBuffer, m_pStringData->StringLength);
|
|
std::memcpy(m_pStringData->pBuffer, pString, Length);
|
|
m_pStringData->StringLength += Length;
|
|
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
|
|
}
|
|
|
|
void String::AppendCharacter(char c)
|
|
{
|
|
InternalAppend(&c, 1);
|
|
}
|
|
|
|
void String::AppendString(const String& appendStr)
|
|
{
|
|
if (appendStr.GetLength() > 0)
|
|
InternalAppend(appendStr.GetCharArray(), appendStr.GetLength());
|
|
}
|
|
|
|
void String::AppendString(const char* appendText)
|
|
{
|
|
u32 textLength = static_cast<u32>(std::strlen(appendText));
|
|
if (textLength > 0)
|
|
InternalAppend(appendText, textLength);
|
|
}
|
|
|
|
void String::AppendString(const char* appendString, u32 Count)
|
|
{
|
|
if (Count > 0)
|
|
InternalAppend(appendString, Count);
|
|
}
|
|
|
|
void String::AppendString(const std::string& appendString)
|
|
{
|
|
if (!appendString.empty())
|
|
InternalAppend(appendString.c_str(), static_cast<u32>(appendString.size()));
|
|
}
|
|
|
|
void String::AppendString(const std::string_view& appendString)
|
|
{
|
|
if (!appendString.empty())
|
|
InternalAppend(appendString.data(), static_cast<u32>(appendString.size()));
|
|
}
|
|
|
|
void String::AppendSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
|
|
{
|
|
u32 appendStrLength = appendStr.GetLength();
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (Offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset);
|
|
else
|
|
realOffset = std::min((u32)Offset, appendStrLength);
|
|
|
|
// calc real count
|
|
u32 realCount;
|
|
if (Count < 0)
|
|
realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count));
|
|
else
|
|
realCount = std::min(appendStrLength - realOffset, (u32)Count);
|
|
|
|
// should be safe
|
|
DebugAssert((realOffset + realCount) <= appendStrLength);
|
|
if (realCount > 0)
|
|
InternalAppend(appendStr.GetCharArray() + realOffset, realCount);
|
|
}
|
|
|
|
void String::AppendSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
|
|
{
|
|
u32 appendTextLength = static_cast<u32>(std::strlen(appendText));
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (Offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset);
|
|
else
|
|
realOffset = std::min((u32)Offset, appendTextLength);
|
|
|
|
// calc real count
|
|
u32 realCount;
|
|
if (Count < 0)
|
|
realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count));
|
|
else
|
|
realCount = std::min(appendTextLength - realOffset, (u32)Count);
|
|
|
|
// should be safe
|
|
DebugAssert((realOffset + realCount) <= appendTextLength);
|
|
if (realCount > 0)
|
|
InternalAppend(appendText + realOffset, realCount);
|
|
}
|
|
|
|
void String::AppendFormattedString(const char* FormatString, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, FormatString);
|
|
AppendFormattedStringVA(FormatString, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void String::AppendFormattedStringVA(const char* FormatString, va_list ArgPtr)
|
|
{
|
|
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
|
// but 1KB should be enough for most strings.
|
|
char stackBuffer[1024];
|
|
char* pHeapBuffer = NULL;
|
|
char* pBuffer = stackBuffer;
|
|
u32 currentBufferSize = countof(stackBuffer);
|
|
u32 charsWritten;
|
|
|
|
for (;;)
|
|
{
|
|
va_list ArgPtrCopy;
|
|
va_copy(ArgPtrCopy, ArgPtr);
|
|
int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtrCopy);
|
|
va_end(ArgPtrCopy);
|
|
if (ret < 0 || ((u32)ret >= (currentBufferSize - 1)))
|
|
{
|
|
currentBufferSize *= 2;
|
|
pBuffer = pHeapBuffer = reinterpret_cast<char*>(std::realloc(pHeapBuffer, currentBufferSize));
|
|
continue;
|
|
}
|
|
|
|
charsWritten = (u32)ret;
|
|
break;
|
|
}
|
|
|
|
InternalAppend(pBuffer, charsWritten);
|
|
|
|
if (pHeapBuffer != NULL)
|
|
std::free(pHeapBuffer);
|
|
}
|
|
|
|
void String::PrependCharacter(char c)
|
|
{
|
|
InternalPrepend(&c, 1);
|
|
}
|
|
|
|
void String::PrependString(const String& appendStr)
|
|
{
|
|
if (appendStr.GetLength() > 0)
|
|
InternalPrepend(appendStr.GetCharArray(), appendStr.GetLength());
|
|
}
|
|
|
|
void String::PrependString(const char* appendText)
|
|
{
|
|
u32 textLength = static_cast<u32>(std::strlen(appendText));
|
|
if (textLength > 0)
|
|
InternalPrepend(appendText, textLength);
|
|
}
|
|
|
|
void String::PrependString(const char* appendString, u32 Count)
|
|
{
|
|
if (Count > 0)
|
|
InternalPrepend(appendString, Count);
|
|
}
|
|
|
|
void String::PrependString(const std::string& appendStr)
|
|
{
|
|
if (!appendStr.empty())
|
|
InternalPrepend(appendStr.c_str(), static_cast<u32>(appendStr.size()));
|
|
}
|
|
|
|
void String::PrependString(const std::string_view& appendStr)
|
|
{
|
|
if (!appendStr.empty())
|
|
InternalPrepend(appendStr.data(), static_cast<u32>(appendStr.size()));
|
|
}
|
|
|
|
void String::PrependSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
|
|
{
|
|
u32 appendStrLength = appendStr.GetLength();
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (Offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset);
|
|
else
|
|
realOffset = std::min((u32)Offset, appendStrLength);
|
|
|
|
// calc real count
|
|
u32 realCount;
|
|
if (Count < 0)
|
|
realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count));
|
|
else
|
|
realCount = std::min(appendStrLength - realOffset, (u32)Count);
|
|
|
|
// should be safe
|
|
DebugAssert((realOffset + realCount) <= appendStrLength);
|
|
if (realCount > 0)
|
|
InternalPrepend(appendStr.GetCharArray() + realOffset, realCount);
|
|
}
|
|
|
|
void String::PrependSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
|
|
{
|
|
u32 appendTextLength = static_cast<u32>(std::strlen(appendText));
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (Offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset);
|
|
else
|
|
realOffset = std::min((u32)Offset, appendTextLength);
|
|
|
|
// calc real count
|
|
u32 realCount;
|
|
if (Count < 0)
|
|
realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count));
|
|
else
|
|
realCount = std::min(appendTextLength - realOffset, (u32)Count);
|
|
|
|
// should be safe
|
|
DebugAssert((realOffset + realCount) <= appendTextLength);
|
|
if (realCount > 0)
|
|
InternalPrepend(appendText + realOffset, realCount);
|
|
}
|
|
|
|
void String::PrependFormattedString(const char* FormatString, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, FormatString);
|
|
PrependFormattedStringVA(FormatString, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void String::PrependFormattedStringVA(const char* FormatString, va_list ArgPtr)
|
|
{
|
|
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
|
// but 1KB should be enough for most strings.
|
|
char stackBuffer[1024];
|
|
char* pHeapBuffer = NULL;
|
|
char* pBuffer = stackBuffer;
|
|
u32 currentBufferSize = countof(stackBuffer);
|
|
u32 charsWritten;
|
|
|
|
for (;;)
|
|
{
|
|
int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtr);
|
|
if (ret < 0 || ((u32)ret >= (currentBufferSize - 1)))
|
|
{
|
|
currentBufferSize *= 2;
|
|
pBuffer = pHeapBuffer = reinterpret_cast<char*>(std::realloc(pHeapBuffer, currentBufferSize));
|
|
continue;
|
|
}
|
|
|
|
charsWritten = (u32)ret;
|
|
break;
|
|
}
|
|
|
|
InternalPrepend(pBuffer, charsWritten);
|
|
|
|
if (pHeapBuffer != NULL)
|
|
std::free(pHeapBuffer);
|
|
}
|
|
|
|
void String::InsertString(s32 offset, const String& appendStr)
|
|
{
|
|
InsertString(offset, appendStr, appendStr.GetLength());
|
|
}
|
|
|
|
void String::InsertString(s32 offset, const char* appendStr)
|
|
{
|
|
InsertString(offset, appendStr, static_cast<u32>(std::strlen(appendStr)));
|
|
}
|
|
|
|
void String::InsertString(s32 offset, const char* appendStr, u32 appendStrLength)
|
|
{
|
|
if (appendStrLength == 0)
|
|
return;
|
|
|
|
EnsureRemainingSpace(appendStrLength);
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)m_pStringData->StringLength + offset);
|
|
else
|
|
realOffset = std::min((u32)offset, m_pStringData->StringLength);
|
|
|
|
// determine number of characters after offset
|
|
DebugAssert(realOffset <= m_pStringData->StringLength);
|
|
u32 charactersAfterOffset = m_pStringData->StringLength - realOffset;
|
|
if (charactersAfterOffset > 0)
|
|
std::memmove(m_pStringData->pBuffer + offset + appendStrLength, m_pStringData->pBuffer + offset,
|
|
charactersAfterOffset);
|
|
|
|
// insert the string
|
|
std::memcpy(m_pStringData->pBuffer + realOffset, appendStr, appendStrLength);
|
|
m_pStringData->StringLength += appendStrLength;
|
|
|
|
// ensure null termination
|
|
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
|
|
}
|
|
|
|
void String::InsertString(s32 offset, const std::string& appendStr)
|
|
{
|
|
InsertString(offset, appendStr.c_str(), static_cast<u32>(appendStr.size()));
|
|
}
|
|
|
|
void String::InsertString(s32 offset, const std::string_view& appendStr)
|
|
{
|
|
InsertString(offset, appendStr.data(), static_cast<u32>(appendStr.size()));
|
|
}
|
|
|
|
void String::Format(const char* FormatString, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, FormatString);
|
|
FormatVA(FormatString, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void String::FormatVA(const char* FormatString, va_list ArgPtr)
|
|
{
|
|
if (GetLength() > 0)
|
|
Clear();
|
|
|
|
AppendFormattedStringVA(FormatString, ArgPtr);
|
|
}
|
|
|
|
void String::Assign(const String& copyString)
|
|
{
|
|
// special case: empty strings
|
|
if (copyString.IsEmpty())
|
|
{
|
|
m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
|
|
}
|
|
// is the string data sharable?
|
|
else if (StringDataIsSharable(copyString.m_pStringData))
|
|
{
|
|
m_pStringData = copyString.m_pStringData;
|
|
if (!m_pStringData->ReadOnly)
|
|
StringDataAddRef(m_pStringData);
|
|
}
|
|
// create a clone for ourselves
|
|
else
|
|
{
|
|
// since we're going to the effort of creating a clone, we might as well create it as the smallest size possible
|
|
m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false);
|
|
}
|
|
}
|
|
|
|
void String::Assign(const char* copyText)
|
|
{
|
|
Clear();
|
|
AppendString(copyText);
|
|
}
|
|
|
|
void String::Assign(String&& moveString)
|
|
{
|
|
Clear();
|
|
m_pStringData = moveString.m_pStringData;
|
|
moveString.m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
|
|
}
|
|
|
|
void String::Assign(const std::string& copyString)
|
|
{
|
|
Clear();
|
|
AppendString(copyString.data(), static_cast<u32>(copyString.size()));
|
|
}
|
|
|
|
void String::Assign(const std::string_view& copyString)
|
|
{
|
|
Clear();
|
|
AppendString(copyString.data(), static_cast<u32>(copyString.size()));
|
|
}
|
|
|
|
void String::AssignCopy(const String& copyString)
|
|
{
|
|
Clear();
|
|
AppendString(copyString);
|
|
}
|
|
|
|
void String::Swap(String& swapString)
|
|
{
|
|
std::swap(m_pStringData, swapString.m_pStringData);
|
|
}
|
|
|
|
bool String::Compare(const String& otherString) const
|
|
{
|
|
return (std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0);
|
|
}
|
|
|
|
bool String::Compare(const char* otherText) const
|
|
{
|
|
return (std::strcmp(m_pStringData->pBuffer, otherText) == 0);
|
|
}
|
|
|
|
bool String::SubCompare(const String& otherString, u32 Length) const
|
|
{
|
|
return (std::strncmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0);
|
|
}
|
|
|
|
bool String::SubCompare(const char* otherText, u32 Length) const
|
|
{
|
|
return (std::strncmp(m_pStringData->pBuffer, otherText, Length) == 0);
|
|
}
|
|
|
|
bool String::CompareInsensitive(const String& otherString) const
|
|
{
|
|
return (CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0);
|
|
}
|
|
|
|
bool String::CompareInsensitive(const char* otherText) const
|
|
{
|
|
return (CASE_COMPARE(m_pStringData->pBuffer, otherText) == 0);
|
|
}
|
|
|
|
bool String::SubCompareInsensitive(const String& otherString, u32 Length) const
|
|
{
|
|
return (CASE_N_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0);
|
|
}
|
|
|
|
bool String::SubCompareInsensitive(const char* otherText, u32 Length) const
|
|
{
|
|
return (CASE_N_COMPARE(m_pStringData->pBuffer, otherText, Length) == 0);
|
|
}
|
|
|
|
int String::NumericCompare(const String& otherString) const
|
|
{
|
|
return std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer);
|
|
}
|
|
|
|
int String::NumericCompare(const char* otherText) const
|
|
{
|
|
return std::strcmp(m_pStringData->pBuffer, otherText);
|
|
}
|
|
|
|
int String::NumericCompareInsensitive(const String& otherString) const
|
|
{
|
|
return CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer);
|
|
}
|
|
|
|
int String::NumericCompareInsensitive(const char* otherText) const
|
|
{
|
|
return CASE_COMPARE(m_pStringData->pBuffer, otherText);
|
|
}
|
|
|
|
bool String::StartsWith(const char* compareString, bool caseSensitive /*= true*/) const
|
|
{
|
|
u32 compareStringLength = static_cast<u32>(std::strlen(compareString));
|
|
if (compareStringLength > m_pStringData->StringLength)
|
|
return false;
|
|
|
|
return (caseSensitive) ? (std::strncmp(compareString, m_pStringData->pBuffer, compareStringLength) == 0) :
|
|
(CASE_N_COMPARE(compareString, m_pStringData->pBuffer, compareStringLength) == 0);
|
|
}
|
|
|
|
bool String::StartsWith(const String& compareString, bool caseSensitive /*= true*/) const
|
|
{
|
|
u32 compareStringLength = compareString.GetLength();
|
|
if (compareStringLength > m_pStringData->StringLength)
|
|
return false;
|
|
|
|
return (caseSensitive) ?
|
|
(std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0) :
|
|
(CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0);
|
|
}
|
|
|
|
bool String::EndsWith(const char* compareString, bool caseSensitive /*= true*/) const
|
|
{
|
|
u32 compareStringLength = static_cast<u32>(std::strlen(compareString));
|
|
if (compareStringLength > m_pStringData->StringLength)
|
|
return false;
|
|
|
|
u32 startOffset = m_pStringData->StringLength - compareStringLength;
|
|
return (caseSensitive) ?
|
|
(std::strncmp(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0) :
|
|
(CASE_N_COMPARE(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0);
|
|
}
|
|
|
|
bool String::EndsWith(const String& compareString, bool caseSensitive /*= true*/) const
|
|
{
|
|
u32 compareStringLength = compareString.GetLength();
|
|
if (compareStringLength > m_pStringData->StringLength)
|
|
return false;
|
|
|
|
u32 startOffset = m_pStringData->StringLength - compareStringLength;
|
|
return (caseSensitive) ? (std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset,
|
|
compareStringLength) == 0) :
|
|
(CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset,
|
|
compareStringLength) == 0);
|
|
}
|
|
|
|
void String::Clear()
|
|
{
|
|
if (m_pStringData == &s_EmptyStringData)
|
|
return;
|
|
|
|
// Do we have a shared buffer? If so, cancel it and allocate a new one when we need to.
|
|
// Otherwise, clear the current buffer.
|
|
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
|
|
{
|
|
// replace with empty string data
|
|
Obliterate();
|
|
}
|
|
else
|
|
{
|
|
// in debug, zero whole string, in release, zero only the first character
|
|
#if _DEBUG
|
|
std::memset(m_pStringData->pBuffer, 0, m_pStringData->BufferSize);
|
|
#else
|
|
m_pStringData->pBuffer[0] = '\0';
|
|
#endif
|
|
m_pStringData->StringLength = 0;
|
|
}
|
|
}
|
|
|
|
void String::Obliterate()
|
|
{
|
|
if (m_pStringData == &s_EmptyStringData)
|
|
return;
|
|
|
|
// Force a release of the current buffer.
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = const_cast<StringData*>(&s_EmptyStringData);
|
|
}
|
|
|
|
s32 String::Find(char c, u32 Offset /* = 0*/) const
|
|
{
|
|
DebugAssert(Offset <= m_pStringData->StringLength);
|
|
char* pAt = std::strchr(m_pStringData->pBuffer + Offset, c);
|
|
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
|
|
}
|
|
|
|
s32 String::RFind(char c, u32 Offset /* = 0*/) const
|
|
{
|
|
DebugAssert(Offset <= m_pStringData->StringLength);
|
|
char* pAt = std::strrchr(m_pStringData->pBuffer + Offset, c);
|
|
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
|
|
}
|
|
|
|
s32 String::Find(const char* str, u32 Offset /* = 0 */) const
|
|
{
|
|
DebugAssert(Offset <= m_pStringData->StringLength);
|
|
char* pAt = std::strstr(m_pStringData->pBuffer + Offset, str);
|
|
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
|
|
}
|
|
|
|
void String::Reserve(u32 newReserve, bool Force /* = false */)
|
|
{
|
|
DebugAssert(!Force || newReserve >= m_pStringData->StringLength);
|
|
|
|
u32 newSize = (Force) ? newReserve + 1 : std::max(newReserve + 1, m_pStringData->BufferSize);
|
|
StringData* pNewStringData;
|
|
|
|
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
|
|
{
|
|
pNewStringData = StringDataClone(m_pStringData, newSize, false);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
}
|
|
else
|
|
{
|
|
// skip if smaller, and not forced
|
|
if (newSize <= m_pStringData->BufferSize && !Force)
|
|
return;
|
|
|
|
// if we are the only owner of the buffer, we can simply realloc it
|
|
if (m_pStringData->ReferenceCount == 1)
|
|
{
|
|
// do realloc and update pointer
|
|
m_pStringData = StringDataReallocate(m_pStringData, newSize);
|
|
}
|
|
else
|
|
{
|
|
// clone and release old
|
|
pNewStringData = StringDataClone(m_pStringData, newSize, false);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
}
|
|
}
|
|
}
|
|
|
|
void String::Resize(u32 newSize, char fillerCharacter /* = ' ' */, bool skrinkIfSmaller /* = false */)
|
|
{
|
|
StringData* pNewStringData;
|
|
|
|
// if going larger, or we don't own the buffer, realloc
|
|
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly || newSize >= m_pStringData->BufferSize)
|
|
{
|
|
pNewStringData = StringDataClone(m_pStringData, newSize + 1, true);
|
|
StringDataRelease(m_pStringData);
|
|
m_pStringData = pNewStringData;
|
|
|
|
if (m_pStringData->StringLength < newSize)
|
|
{
|
|
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, fillerCharacter,
|
|
m_pStringData->BufferSize - m_pStringData->StringLength - 1);
|
|
}
|
|
|
|
m_pStringData->StringLength = newSize;
|
|
}
|
|
else
|
|
{
|
|
// owns the buffer, and going smaller
|
|
DebugAssert(newSize < m_pStringData->BufferSize);
|
|
|
|
// update length and terminator
|
|
#if _DEBUG
|
|
std::memset(m_pStringData->pBuffer + newSize, 0, m_pStringData->BufferSize - newSize);
|
|
#else
|
|
m_pStringData->pBuffer[newSize] = 0;
|
|
#endif
|
|
m_pStringData->StringLength = newSize;
|
|
|
|
// shrink if requested
|
|
if (skrinkIfSmaller)
|
|
Shrink(false);
|
|
}
|
|
}
|
|
|
|
void String::UpdateSize()
|
|
{
|
|
EnsureOwnWritableCopy();
|
|
m_pStringData->StringLength = static_cast<u32>(std::strlen(m_pStringData->pBuffer));
|
|
}
|
|
|
|
void String::Shrink(bool Force /* = false */)
|
|
{
|
|
// only shrink of we own the buffer, or forced
|
|
if (Force || m_pStringData->ReferenceCount == 1)
|
|
Reserve(m_pStringData->StringLength);
|
|
}
|
|
|
|
String String::SubString(s32 Offset, s32 Count /* = -1 */) const
|
|
{
|
|
String returnStr;
|
|
returnStr.AppendSubString(*this, Offset, Count);
|
|
return returnStr;
|
|
}
|
|
|
|
void String::Erase(s32 Offset, s32 Count /* = INT_std::max */)
|
|
{
|
|
u32 currentLength = m_pStringData->StringLength;
|
|
|
|
// calc real offset
|
|
u32 realOffset;
|
|
if (Offset < 0)
|
|
realOffset = (u32)std::max((s32)0, (s32)currentLength + Offset);
|
|
else
|
|
realOffset = std::min((u32)Offset, currentLength);
|
|
|
|
// calc real count
|
|
u32 realCount;
|
|
if (Count < 0)
|
|
realCount = std::min(currentLength - realOffset, (u32)std::max((s32)0, (s32)currentLength + Count));
|
|
else
|
|
realCount = std::min(currentLength - realOffset, (u32)Count);
|
|
|
|
// Fastpath: offset == 0, count < 0, wipe whole string.
|
|
if (realOffset == 0 && realCount == currentLength)
|
|
{
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
// Fastpath: offset >= 0, count < 0, wipe everything after offset + count
|
|
if ((realOffset + realCount) == m_pStringData->StringLength)
|
|
{
|
|
m_pStringData->StringLength -= realCount;
|
|
#ifdef _DEBUG
|
|
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0,
|
|
m_pStringData->BufferSize - m_pStringData->StringLength);
|
|
#else
|
|
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
|
|
#endif
|
|
}
|
|
// Slowpath: offset >= 0, count < length
|
|
else
|
|
{
|
|
u32 afterEraseBlock = m_pStringData->StringLength - realOffset - realCount;
|
|
DebugAssert(afterEraseBlock > 0);
|
|
|
|
std::memmove(m_pStringData->pBuffer + Offset, m_pStringData->pBuffer + realOffset + realCount, afterEraseBlock);
|
|
m_pStringData->StringLength = m_pStringData->StringLength - realCount;
|
|
|
|
#ifdef _DEBUG
|
|
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0,
|
|
m_pStringData->BufferSize - m_pStringData->StringLength);
|
|
#else
|
|
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
u32 String::Replace(char searchCharacter, char replaceCharacter)
|
|
{
|
|
u32 nReplacements = 0;
|
|
char* pCurrent = std::strchr(m_pStringData->pBuffer, searchCharacter);
|
|
while (pCurrent != NULL)
|
|
{
|
|
if ((nReplacements++) == 0)
|
|
EnsureOwnWritableCopy();
|
|
|
|
*pCurrent = replaceCharacter;
|
|
pCurrent = std::strchr(pCurrent + 1, searchCharacter);
|
|
}
|
|
|
|
return nReplacements;
|
|
}
|
|
|
|
u32 String::Replace(const char* searchString, const char* replaceString)
|
|
{
|
|
u32 nReplacements = 0;
|
|
u32 searchStringLength = static_cast<u32>(std::strlen(searchString));
|
|
|
|
#if 0
|
|
u32 replaceStringLength = static_cast<u32>(std::strlen(replaceString));
|
|
s32 lengthDifference = (s32)replaceStringLength - (s32)searchStringLength;
|
|
|
|
char *pCurrent = std::strchr(m_pStringData->pBuffer, searchString);
|
|
while (pCurrent != NULL)
|
|
{
|
|
if ((nReplacements++) == 0)
|
|
{
|
|
if (lengthDifference > 0)
|
|
EnsureRemainingSpace(lengthDifference);
|
|
else
|
|
EnsureOwnCopy();
|
|
}
|
|
else if (lengthDifference > 0)
|
|
EnsureRemainingSpace(lengthDifference);
|
|
}
|
|
#endif
|
|
|
|
// TODO: Fastpath if strlen(searchString) == strlen(replaceString)
|
|
|
|
String tempString;
|
|
char* pStart = m_pStringData->pBuffer;
|
|
char* pCurrent = std::strstr(pStart, searchString);
|
|
char* pLast = NULL;
|
|
while (pCurrent != NULL)
|
|
{
|
|
if ((nReplacements++) == 0)
|
|
tempString.Reserve(m_pStringData->StringLength);
|
|
|
|
tempString.AppendSubString(*this, s32(pStart - pCurrent), s32(pStart - pCurrent - 1));
|
|
tempString.AppendString(replaceString);
|
|
pLast = pCurrent + searchStringLength;
|
|
nReplacements++;
|
|
|
|
pCurrent = std::strstr(pLast, searchString);
|
|
}
|
|
|
|
if (pLast != NULL)
|
|
tempString.AppendSubString(*this, s32(pLast - pStart));
|
|
|
|
if (nReplacements)
|
|
Swap(tempString);
|
|
|
|
return nReplacements;
|
|
}
|
|
|
|
void String::ToLower()
|
|
{
|
|
// fixme for utf8
|
|
EnsureOwnWritableCopy();
|
|
for (u32 i = 0; i < m_pStringData->StringLength; i++)
|
|
{
|
|
if (std::isprint(m_pStringData->pBuffer[i]))
|
|
m_pStringData->pBuffer[i] = static_cast<char>(std::tolower(m_pStringData->pBuffer[i]));
|
|
}
|
|
}
|
|
|
|
void String::ToUpper()
|
|
{
|
|
// fixme for utf8
|
|
EnsureOwnWritableCopy();
|
|
for (u32 i = 0; i < m_pStringData->StringLength; i++)
|
|
{
|
|
if (std::isprint(m_pStringData->pBuffer[i]))
|
|
m_pStringData->pBuffer[i] = static_cast<char>(std::toupper(m_pStringData->pBuffer[i]));
|
|
}
|
|
}
|
|
|
|
void String::LStrip(const char* szStripCharacters /* = " " */)
|
|
{
|
|
u32 stripCharactersLen = static_cast<u32>(std::strlen(szStripCharacters));
|
|
u32 removeCount = 0;
|
|
u32 i = 0;
|
|
u32 j;
|
|
|
|
// for each character in str
|
|
for (i = 0; i < m_pStringData->StringLength; i++)
|
|
{
|
|
char ch = m_pStringData->pBuffer[i];
|
|
|
|
// if it exists in szStripCharacters
|
|
for (j = 0; j < stripCharactersLen; j++)
|
|
{
|
|
if (ch == szStripCharacters[j])
|
|
{
|
|
removeCount++;
|
|
goto OUTER;
|
|
}
|
|
}
|
|
|
|
// not found, exit
|
|
break;
|
|
OUTER:
|
|
continue;
|
|
}
|
|
|
|
// chars to remove?
|
|
if (removeCount > 0)
|
|
Erase(0, removeCount);
|
|
}
|
|
|
|
void String::RStrip(const char* szStripCharacters /* = " " */)
|
|
{
|
|
u32 stripCharactersLen = static_cast<u32>(std::strlen(szStripCharacters));
|
|
u32 removeCount = 0;
|
|
u32 i = 0;
|
|
u32 j;
|
|
|
|
// for each character in str
|
|
for (i = 0; i < m_pStringData->StringLength; i++)
|
|
{
|
|
char ch = m_pStringData->pBuffer[m_pStringData->StringLength - i - 1];
|
|
|
|
// if it exists in szStripCharacters
|
|
for (j = 0; j < stripCharactersLen; j++)
|
|
{
|
|
if (ch == szStripCharacters[j])
|
|
{
|
|
removeCount++;
|
|
goto OUTER;
|
|
}
|
|
}
|
|
|
|
// not found, exit
|
|
break;
|
|
OUTER:
|
|
continue;
|
|
}
|
|
|
|
// chars to remove?
|
|
if (removeCount > 0)
|
|
Erase(m_pStringData->StringLength - removeCount);
|
|
}
|
|
|
|
void String::Strip(const char* szStripCharacters /* = " " */)
|
|
{
|
|
RStrip(szStripCharacters);
|
|
LStrip(szStripCharacters);
|
|
}
|
|
|
|
String String::FromFormat(const char* FormatString, ...)
|
|
{
|
|
String returnStr;
|
|
va_list ap;
|
|
|
|
va_start(ap, FormatString);
|
|
returnStr.FormatVA(FormatString, ap);
|
|
va_end(ap);
|
|
|
|
return returnStr;
|
|
}
|