From b030344c1035e1bb2b179ccd02dad4bdda0349dc Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Fri, 17 Mar 2023 00:08:16 +0100 Subject: [PATCH] Add 'CUtlString' to SDK Add 'CUtlString' from Source SDK. --- r5dev/tier1/utlstring.cpp | 1214 ++++++++++++++++++++++++++++++ r5dev/tier1/utlstring.h | 1488 +++++++++++++++++++++++++++++++++++++ 2 files changed, 2702 insertions(+) create mode 100644 r5dev/tier1/utlstring.cpp create mode 100644 r5dev/tier1/utlstring.h diff --git a/r5dev/tier1/utlstring.cpp b/r5dev/tier1/utlstring.cpp new file mode 100644 index 00000000..b694190d --- /dev/null +++ b/r5dev/tier1/utlstring.cpp @@ -0,0 +1,1214 @@ +//========= Copyright (c) Valve Corporation, All rights reserved. ============= +// +// Purpose: CUtlBinaryBlock and CUtlString implementation +// +//============================================================================= + +#include "tier1/utlstring.h" +#include "tier1/utlvector.h" +#include "tier1/strtools.h" +#include + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +static const int64 k_nMillion = 1000000; + +//----------------------------------------------------------------------------- +// Purpose: Helper: Find s substring +//----------------------------------------------------------------------------- +static ptrdiff_t IndexOf(const char *pstrToSearch, const char *pstrTarget) +{ + const char *pstrHit = V_strstr(pstrToSearch, pstrTarget); + if (pstrHit == NULL) + { + return -1; // Not found. + } + return (pstrHit - pstrToSearch); +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper: kill all whitespace. +//----------------------------------------------------------------------------- +static size_t RemoveWhitespace(char *pszString) +{ + if (pszString == NULL) + return 0; + + char *pstrDest = pszString; + size_t cRemoved = 0; + for (char *pstrWalker = pszString; *pstrWalker != 0; pstrWalker++) + { + if (!V_isspace((unsigned char)*pstrWalker)) + { + *pstrDest = *pstrWalker; + pstrDest++; + } + else + cRemoved += 1; + } + *pstrDest = 0; + + return cRemoved; +} + +int V_vscprintf(const char *format, va_list params) +{ +#ifdef _WIN32 + return _vscprintf(format, params); +#else + return vsnprintf(NULL, 0, format, params); +#endif +} + +//----------------------------------------------------------------------------- +// Base class, containing simple memory management +//----------------------------------------------------------------------------- +CUtlBinaryBlock::CUtlBinaryBlock( int growSize, int initSize ) +{ + MEM_ALLOC_CREDIT(); + m_Memory.Init( growSize, initSize ); + + m_nActualLength = 0; +} + +CUtlBinaryBlock::CUtlBinaryBlock( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Memory( (unsigned char*)pMemory, nSizeInBytes ) +{ + m_nActualLength = nInitialLength; +} + +CUtlBinaryBlock::CUtlBinaryBlock( const void* pMemory, int nSizeInBytes ) : m_Memory( (const unsigned char*)pMemory, nSizeInBytes ) +{ + m_nActualLength = nSizeInBytes; +} + +CUtlBinaryBlock::CUtlBinaryBlock( const CUtlBinaryBlock& src ) +{ + Set( src.Get(), src.Length() ); +} + +void CUtlBinaryBlock::Get( void *pValue, int nLen ) const +{ + Assert( nLen > 0 ); + if ( m_nActualLength < nLen ) + { + nLen = m_nActualLength; + } + + if ( nLen > 0 ) + { + memcpy( pValue, m_Memory.Base(), nLen ); + } +} + +void CUtlBinaryBlock::SetLength( int nLength ) +{ + MEM_ALLOC_CREDIT(); + Assert( !m_Memory.IsReadOnly() ); + + m_nActualLength = nLength; + if ( nLength > m_Memory.NumAllocated() ) + { + int nOverFlow = nLength - m_Memory.NumAllocated(); + m_Memory.Grow( nOverFlow ); + + // If the reallocation failed, clamp length + if ( nLength > m_Memory.NumAllocated() ) + { + m_nActualLength = m_Memory.NumAllocated(); + } + } + +#ifdef _DEBUG + if ( m_Memory.NumAllocated() > m_nActualLength ) + { + memset( ( ( char * )m_Memory.Base() ) + m_nActualLength, 0xEB, m_Memory.NumAllocated() - m_nActualLength ); + } +#endif +} + + +void CUtlBinaryBlock::Set( const void *pValue, int nLen ) +{ + Assert( !m_Memory.IsReadOnly() ); + + if ( !pValue ) + { + nLen = 0; + } + + SetLength( nLen ); + + if ( m_nActualLength ) + { + if ( ( ( const char * )m_Memory.Base() ) >= ( ( const char * )pValue ) + nLen || + ( ( const char * )m_Memory.Base() ) + m_nActualLength <= ( ( const char * )pValue ) ) + { + memcpy( m_Memory.Base(), pValue, m_nActualLength ); + } + else + { + memmove( m_Memory.Base(), pValue, m_nActualLength ); + } + } +} + + +CUtlBinaryBlock &CUtlBinaryBlock::operator=( const CUtlBinaryBlock &src ) +{ + Assert( !m_Memory.IsReadOnly() ); + Set( src.Get(), src.Length() ); + return *this; +} + + +bool CUtlBinaryBlock::operator==( const CUtlBinaryBlock &src ) const +{ + if ( src.Length() != Length() ) + return false; + + return !memcmp( src.Get(), Get(), Length() ); +} + + +//----------------------------------------------------------------------------- +// Simple string class. +//----------------------------------------------------------------------------- +CUtlString::CUtlString() +{ +} + +CUtlString::CUtlString( const char *pString ) +{ + Set( pString ); +} + +CUtlString::CUtlString( const CUtlString& string ) +{ + Set( string.Get() ); +} + +// Attaches the string to external memory. Useful for avoiding a copy +CUtlString::CUtlString( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Storage( pMemory, nSizeInBytes, nInitialLength ) +{ +} + +CUtlString::CUtlString( const void* pMemory, int nSizeInBytes ) : m_Storage( pMemory, nSizeInBytes ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Set directly and don't look for a null terminator in pValue. +//----------------------------------------------------------------------------- +void CUtlString::SetDirect( const char *pValue, int nChars ) +{ + if ( nChars > 0 ) + { + m_Storage.SetLength( nChars+1 ); + m_Storage.Set( pValue, nChars+1 ); + m_Storage[nChars] = 0; + } + else + { + m_Storage.SetLength( 0 ); + } +} + + +void CUtlString::Set( const char *pValue ) +{ + Assert( !m_Storage.IsReadOnly() ); + int nLen = pValue ? Q_strlen(pValue) + 1 : 0; + m_Storage.Set( pValue, nLen ); +} + + +// Returns strlen +int CUtlString::Length() const +{ + return m_Storage.Length() ? m_Storage.Length() - 1 : 0; +} + +// Sets the length (used to serialize into the buffer ) +void CUtlString::SetLength( int nLen ) +{ + Assert( !m_Storage.IsReadOnly() ); + + // Add 1 to account for the NULL + m_Storage.SetLength( nLen > 0 ? nLen + 1 : 0 ); +} + +const char *CUtlString::Get( ) const +{ + if ( m_Storage.Length() == 0 ) + { + return ""; + } + + return reinterpret_cast< const char* >( m_Storage.Get() ); +} + +char *CUtlString::Get() +{ + Assert( !m_Storage.IsReadOnly() ); + + if ( m_Storage.Length() == 0 ) + { + // In general, we optimise away small mallocs for empty strings + // but if you ask for the non-const bytes, they must be writable + // so we can't return "" here, like we do for the const version - jd + m_Storage.SetLength( 1 ); + m_Storage[ 0 ] = '\0'; + } + + return reinterpret_cast< char* >( m_Storage.Get() ); +} + +char *CUtlString::GetForModify() +{ + return Get(); +} + +void CUtlString::Purge() +{ + m_Storage.Purge(); +} + +void CUtlString::ToUpper() +{ + for (int nLength = Length() - 1; nLength >= 0; nLength--) + { + m_Storage[nLength] = toupper(m_Storage[nLength]); + } +} + +void CUtlString::ToLower() +{ + for( int nLength = Length() - 1; nLength >= 0; nLength-- ) + { + m_Storage[ nLength ] = tolower( m_Storage[ nLength ] ); + } +} + + +CUtlString &CUtlString::operator=( const CUtlString &src ) +{ + Assert( !m_Storage.IsReadOnly() ); + m_Storage = src.m_Storage; + return *this; +} + +CUtlString &CUtlString::operator=( const char *src ) +{ + Assert( !m_Storage.IsReadOnly() ); + Set( src ); + return *this; +} + +bool CUtlString::operator==( const CUtlString &src ) const +{ + return m_Storage == src.m_Storage; +} + +bool CUtlString::operator==( const char *src ) const +{ + return ( strcmp( Get(), src ) == 0 ); +} + +CUtlString &CUtlString::operator+=( const CUtlString &rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + const int lhsLength( Length() ); + const int rhsLength( rhs.Length() ); + const int requestedLength( lhsLength + rhsLength ); + + SetLength( requestedLength ); + const int allocatedLength( Length() ); + const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength ); + memcpy( Get() + lhsLength, rhs.Get(), copyLength ); + m_Storage[ allocatedLength ] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=( const char *rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + const int lhsLength( Length() ); + const int rhsLength( Q_strlen( rhs ) ); + const int requestedLength( lhsLength + rhsLength ); + + SetLength( requestedLength ); + const int allocatedLength( Length() ); + const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength ); + memcpy( Get() + lhsLength, rhs, copyLength ); + m_Storage[ allocatedLength ] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=( char c ) +{ + Assert( !m_Storage.IsReadOnly() ); + + int nLength = Length(); + SetLength( nLength + 1 ); + m_Storage[ nLength ] = c; + m_Storage[ nLength+1 ] = '\0'; + return *this; +} + +CUtlString &CUtlString::operator+=( int rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + Assert( sizeof( rhs ) == 4 ); + + char tmpBuf[ 12 ]; // Sufficient for a signed 32 bit integer [ -2147483648 to +2147483647 ] + Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%d", rhs ); + tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0'; + + return operator+=( tmpBuf ); +} + +CUtlString &CUtlString::operator+=( double rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + char tmpBuf[ 256 ]; // How big can doubles be??? Dunno. + Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%lg", rhs ); + tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0'; + + return operator+=( tmpBuf ); +} + +bool CUtlString::MatchesPattern( const CUtlString &Pattern, int nFlags ) +{ + const char *pszSource = String(); + const char *pszPattern = Pattern.String(); + + return V_StringMatchesPattern( pszSource, pszPattern, nFlags ); +} + + +int CUtlString::Format( const char *pFormat, ... ) +{ + va_list marker; + + va_start( marker, pFormat ); + int len = FormatV( pFormat, marker ); + va_end( marker ); + + return len; +} + +//-------------------------------------------------------------------------------------------------- +// This can be called from functions that take varargs. +//-------------------------------------------------------------------------------------------------- + +int CUtlString::FormatV( const char *pFormat, va_list marker ) +{ + char tmpBuf[ 4096 ]; //< Nice big 4k buffer, as much memory as my first computer had, a Radio Shack Color Computer + + //va_start( marker, pFormat ); + int len = V_vsprintf_safe( tmpBuf, pFormat, marker ); + //va_end( marker ); + Set( tmpBuf ); + return len; +} + +//----------------------------------------------------------------------------- +// Strips the trailing slash +//----------------------------------------------------------------------------- +void CUtlString::StripTrailingSlash() +{ + if ( IsEmpty() ) + return; + + int nLastChar = Length() - 1; + char c = m_Storage[ nLastChar ]; + if ( c == '\\' || c == '/' ) + { + m_Storage[ nLastChar ] = 0; + m_Storage.SetLength( m_Storage.Length() - 1 ); + } +} + +CUtlString CUtlString::Slice( int32 nStart, int32 nEnd ) +{ + if ( nStart < 0 ) + nStart = Length() - (-nStart % Length()); + else if ( nStart >= Length() ) + nStart = Length(); + + if ( nEnd == INT32_MAX ) + nEnd = Length(); + else if ( nEnd < 0 ) + nEnd = Length() - (-nEnd % Length()); + else if ( nEnd >= Length() ) + nEnd = Length(); + + if ( nStart >= nEnd ) + return CUtlString( "" ); + + const char *pIn = String(); + + CUtlString ret; + ret.m_Storage.SetLength( nEnd - nStart + 1 ); + char *pOut = (char*)ret.m_Storage.Get(); + + memcpy( ret.m_Storage.Get(), &pIn[nStart], nEnd - nStart ); + pOut[nEnd - nStart] = 0; + + return ret; +} + +// Grab a substring starting from the left or the right side. +CUtlString CUtlString::Left( int32 nChars ) +{ + return Slice( 0, nChars ); +} + +CUtlString CUtlString::Right( int32 nChars ) +{ + return Slice( -nChars ); +} + +// Get a string with the specified substring removed + +CUtlString CUtlString::Remove( char const *pTextToRemove, bool bCaseSensitive ) const +{ + int nTextToRemoveLength = pTextToRemove ? V_strlen( pTextToRemove ) : 0; + CUtlString outputString; + const char *pSrc = Get(); + if ( pSrc ) + { + while ( *pSrc ) + { + char const *pNextOccurrence = bCaseSensitive ? V_strstr( pSrc, pTextToRemove ) : V_stristr( pSrc, pTextToRemove ); + if ( !pNextOccurrence ) + { + // append remaining string + outputString += pSrc; + break; + } + + int nNumCharsToCopy = pNextOccurrence - pSrc; + if ( nNumCharsToCopy ) + { + // append up to the undesired substring + CUtlString temp = pSrc; + temp = temp.Left( nNumCharsToCopy ); + outputString += temp; + } + + // skip past undesired substring + pSrc = pNextOccurrence + nTextToRemoveLength; + } + } + + return outputString; +} + +CUtlString CUtlString::Replace( char const *pchFrom, const char *pchTo, bool bCaseSensitive /*= false*/ ) const +{ + if ( !pchTo ) + { + return Remove( pchFrom, bCaseSensitive ); + } + + int nTextToReplaceLength = pchFrom ? V_strlen( pchFrom ) : 0; + CUtlString outputString; + const char *pSrc = Get(); + if ( pSrc ) + { + while ( *pSrc ) + { + char const *pNextOccurrence = bCaseSensitive ? V_strstr( pSrc, pchFrom ) : V_stristr( pSrc, pchFrom ); + if ( !pNextOccurrence ) + { + // append remaining string + outputString += pSrc; + break; + } + + int nNumCharsToCopy = pNextOccurrence - pSrc; + if ( nNumCharsToCopy ) + { + // append up to the undesired substring + CUtlString temp = pSrc; + temp = temp.Left( nNumCharsToCopy ); + outputString += temp; + } + + // Append the replacement + outputString += pchTo; + + // skip past undesired substring + pSrc = pNextOccurrence + nTextToReplaceLength; + } + } + + return outputString; + +} + + + +CUtlString CUtlString::Replace( char cFrom, char cTo ) +{ + CUtlString ret = *this; + int len = ret.Length(); + for ( int i=0; i < len; i++ ) + { + if ( ret.m_Storage[i] == cFrom ) + ret.m_Storage[i] = cTo; + } + + return ret; +} + +void CUtlString::RemoveDotSlashes(char separator) +{ + V_RemoveDotSlashes(Get(), separator); +} + +void CUtlString::Swap( CUtlString &src ) +{ + CUtlString tmp = src; + src = *this; + *this = tmp; +} + + +CUtlString CUtlString::AbsPath( const char *pStartingDir ) const +{ + char szNew[MAX_PATH]; + V_MakeAbsolutePath( szNew, sizeof( szNew ), this->String(), pStartingDir ); + return CUtlString( szNew ); +} + +CUtlString CUtlString::UnqualifiedFilename() const +{ + const char *pFilename = V_UnqualifiedFileName( this->String() ); + return CUtlString( pFilename ); +} + +CUtlString CUtlString::DirName() +{ + CUtlString ret( this->String() ); + V_StripLastDir( (char*)ret.m_Storage.Get(), ret.m_Storage.Length() ); + V_StripTrailingSlash( (char*)ret.m_Storage.Get() ); + return ret; +} + +CUtlString CUtlString::StripExtension() const +{ + char szTemp[MAX_FILEPATH]; + V_StripExtension( String(), szTemp, sizeof( szTemp ) ); + return CUtlString( szTemp ); +} + +CUtlString CUtlString::StripFilename() const +{ + const char *pFilename = V_UnqualifiedFileName( Get() ); // NOTE: returns 'Get()' on failure, never NULL + int nCharsToCopy = pFilename - Get(); + CUtlString result; + result.SetDirect( Get(), nCharsToCopy ); + result.StripTrailingSlash(); + return result; +} + +CUtlString CUtlString::GetBaseFilename() const +{ + char szTemp[MAX_FILEPATH]; + V_FileBase( String(), szTemp, sizeof( szTemp ) ); + return CUtlString( szTemp ); +} + +CUtlString CUtlString::GetExtension() const +{ + char szTemp[MAX_FILEPATH]; + V_ExtractFileExtension( String(), szTemp, sizeof( szTemp ) ); + return CUtlString( szTemp ); +} + + +CUtlString CUtlString::PathJoin( const char *pStr1, const char *pStr2 ) +{ + char szPath[MAX_PATH]; + V_ComposeFileName( pStr1, pStr2, szPath, sizeof( szPath ) ); + return CUtlString( szPath ); +} + + +//----------------------------------------------------------------------------- +// Purpose: concatenate the provided string to our current content +//----------------------------------------------------------------------------- +void CUtlString::Append( const char *pchAddition ) +{ + (*this) += pchAddition; +} + +void CUtlString::Append(const char *pchAddition, int nMaxChars) +{ + const int nLen = V_strlen(pchAddition); + if (nMaxChars < 0 || nLen <= nMaxChars) + { + Append(pchAddition); + } + else + { + char* pchAdditionDup = V_strdup(pchAddition); + pchAdditionDup[nMaxChars] = 0; + Append(pchAdditionDup); + delete[] pchAdditionDup; + } +} + +//-------------------------------------------------------------------------------------------------- +// Trim +//-------------------------------------------------------------------------------------------------- + +void CUtlString::TrimLeft( const char *szTargets ) +{ + int i; + + if ( IsEmpty() ) + { + return; + } + + char* pSrc = Get(); + + for ( i = 0; pSrc[ i ] != 0; i++ ) + { + bool bWhitespace = false; + + for ( int j = 0; szTargets[ j ] != 0; j++ ) + { + if ( pSrc[ i ] == szTargets[ j ] ) + { + bWhitespace = true; + break; + } + } + + if ( !bWhitespace ) + { + break; + } + } + + // We have some whitespace to remove + if ( i > 0 ) + { + memmove( pSrc, &pSrc[ i ], Length() - i ); + SetLength( Length() - i ); + m_Storage[ Length() ] = '\0'; + } +} + +void CUtlString::TrimRight( const char *szTargets ) +{ + const int nLastCharIndex = Length() - 1; + int i; + + char* pSrc = Get(); + + for ( i = nLastCharIndex; i >= 0; i-- ) + { + bool bWhitespace = false; + + for ( int j = 0; szTargets[ j ] != 0; j++ ) + { + if ( pSrc[ i ] == szTargets[ j ] ) + { + bWhitespace = true; + break; + } + } + + if ( !bWhitespace ) + { + break; + } + } + + // We have some whitespace to remove + if ( i < nLastCharIndex ) + { + pSrc[ i + 1 ] = 0; + SetLength( i + 1 ); + } +} + +void CUtlString::Trim( const char *szTargets ) +{ + TrimLeft( szTargets ); + TrimRight( szTargets ); +} + + +//----------------------------------------------------------------------------- +// Purpose: spill routine for making sure our buffer is big enough for an +// incoming string set/modify. +//----------------------------------------------------------------------------- +char *CUtlStringBuilder::InternalPrepareBuffer(size_t nChars, bool bCopyOld, size_t nMinCapacity) +{ + Assert(nMinCapacity > Capacity()); + Assert(nMinCapacity >= nChars); + // Don't use this class if you want a single 2GB+ string. + static const size_t k_nMaxStringSize = 0x7FFFFFFFu; + Assert(nMinCapacity <= k_nMaxStringSize); + + if (nMinCapacity > k_nMaxStringSize) + { + SetError(); + return NULL; + } + + bool bWasHeap = m_data.IsHeap(); + // add this to whatever we are going to grow so we don't start out too slow + char *pszString = NULL; + if (nMinCapacity > MAX_STACK_STRLEN) + { + // Allocate 1.5 times what is requested, plus a small initial ramp + // value so we don't spend too much time re-allocating tiny buffers. + // A good allocator will prevent this anyways, but this makes it safer. + // We cap it at +1 million to not get crazy. Code actually avoides + // computing power of two numbers since allocations almost always + // have header/bookkeeping overhead. Don't do the dynamic sizing + // if the user asked for a specific capacity. + static const int k_nInitialMinRamp = 32; + size_t nNewSize; + if (nMinCapacity > nChars) + nNewSize = nMinCapacity; + else + nNewSize = nChars + Min((nChars >> 1) + k_nInitialMinRamp, k_nMillion); + + char *pszOld = m_data.Access(); + size_t nLenOld = m_data.Length(); + + // order of operations is very important per comment + // above. Make sure we copy it before changing m_data + // in any way + if (bWasHeap && bCopyOld) + { + // maybe we'll get lucky and get the same buffer back. + pszString = (char*)realloc(pszOld, nNewSize + 1); + if (!pszString) + { + SetError(); + return NULL; + } + } + else // Either it's already on the stack; or we don't need to copy + { + // if the current pointer is on the heap, we aren't doing a copy + // (or we would have used the previous realloc code. So + // if we aren't doing a copy, don't use realloc since it will + // copy the data if it needs to make a new allocation. + if (bWasHeap) + free(pszOld); + + pszString = (char*)malloc(nNewSize + 1); + if (!pszString) + { + SetError(); + return NULL; + } + + // still need to do the copy if we are going from small buffer to large + if (bCopyOld) + memcpy(pszString, pszOld, nLenOld); // null will be added at end of func. + } + + // just in case the user grabs .Access() and scribbles over the terminator at + // 'length', make sure they don't run off the rails as long as they obey Capacity. + // We don't offer this protection for the 'on stack' string. + pszString[nNewSize] = '\0'; + + m_data.Heap.m_pchString = pszString; + m_data.Heap.m_nCapacity = (uint32)nNewSize; // capacity is the max #chars, not including the null. + m_data.Heap.m_nLength = (uint32)nChars; + m_data.Heap.sentinel = STRING_TYPE_SENTINEL; + } + else + { + // Rare case. Only happens if someone did a SetPtr with a length + // less than MAX_STACK_STRLEN, or maybe a .Replace() shrunk the + // length down. + pszString = m_data.Stack.m_szString; + m_data.Stack.SetBytesLeft(MAX_STACK_STRLEN - (uint8)nChars); + + if (bWasHeap) + { + char *pszOldString = m_data.Heap.m_pchString; + if (bCopyOld) + memcpy(pszString, pszOldString, nChars); // null will be added at end of func. + + free(pszOldString); + } + } + + pszString[nChars] = '\0'; + return pszString; +} + + +//----------------------------------------------------------------------------- +// Purpose: replace all occurrences of one string with another +// replacement string may be NULL or "" to remove target string +//----------------------------------------------------------------------------- +size_t CUtlStringBuilder::Replace(const char *pstrTarget, const char *pstrReplacement) +{ + return ReplaceInternal(pstrTarget, pstrReplacement, (const char *(*)(const char *, const char *))_V_strstr); +} + + +//----------------------------------------------------------------------------- +// Purpose: replace all occurrences of one string with another +// replacement string may be NULL or "" to remove target string +//----------------------------------------------------------------------------- +size_t CUtlStringBuilder::ReplaceFastCaseless(const char *pstrTarget, const char *pstrReplacement) +{ + return ReplaceInternal(pstrTarget, pstrReplacement, V_stristr_fast); +} + + +//----------------------------------------------------------------------------- +// Purpose: replace all occurrences of one string with another +// replacement string may be NULL or "" to remove target string +//----------------------------------------------------------------------------- +size_t CUtlStringBuilder::ReplaceInternal(const char *pstrTarget, const char *pstrReplacement, const char *pfnCompare(const char*, const char*)) +{ + if (HasError()) + return 0; + + if (pstrReplacement == NULL) + pstrReplacement = ""; + + size_t nTargetLength = V_strlen(pstrTarget); + size_t nReplacementLength = V_strlen(pstrReplacement); + + CUtlVector vecMatches; + vecMatches.EnsureCapacity(8); + + if (!IsEmpty() && pstrTarget && *pstrTarget) + { + char *pszString = Access(); + + // walk the string counting hits + const char *pstrHit = pszString; + for (pstrHit = pfnCompare(pstrHit, pstrTarget); pstrHit != NULL && *pstrHit != 0; /* inside */) + { + vecMatches.AddToTail(pstrHit); + // look for the next target and keep looping + pstrHit = pfnCompare(pstrHit + nTargetLength, pstrTarget); + } + + // if we didn't miss, get to work + if (vecMatches.Count() > 0) + { + // reallocate only once; how big will we need? + size_t nOldLength = Length(); + size_t nNewLength = nOldLength + (vecMatches.Count() * (int)(nReplacementLength - nTargetLength)); + + if (nNewLength == 0) + { + // shortcut simple case, even if rare + m_data.Clear(); + } + else if (nNewLength > nOldLength) + { + // New string will be bigger than the old, but don't re-alloc unless + // it is also larger than capacity. If it fits in capacity, we will + // be adjusting the string 'in place'. The replacement string is larger + // than the target string, so if we copied front to back we would screw up + // the existing data in the 'in place' case. + char *pstrNew; + if (nNewLength > Capacity()) + { + pstrNew = (char*)malloc(nNewLength + 1); + if (!pstrNew) + { + SetError(); + return 0; + } + } + else + { + pstrNew = PrepareBuffer(nNewLength); + Assert(pstrNew == pszString); + } + + const char *pstrPreviousHit = pszString + nOldLength; // end of original string + char *pstrDestination = pstrNew + nNewLength; // end of target + *pstrDestination = '\0'; + // Go backwards as noted above. + FOR_EACH_VEC_BACK(vecMatches, i) + { + pstrHit = vecMatches[i]; + size_t nRemainder = pstrPreviousHit - (pstrHit + nTargetLength); + // copy the bit after the match, back up the destination and move forward from the hit + memmove(pstrDestination - nRemainder, pstrPreviousHit - nRemainder, nRemainder); + pstrDestination -= (nRemainder + nReplacementLength); + + // push the replacement string in + memcpy(pstrDestination, pstrReplacement, nReplacementLength); + pstrPreviousHit = pstrHit; + } + + // copy trailing stuff + size_t nRemainder = pstrPreviousHit - pszString; + pstrDestination -= nRemainder; + if (pstrDestination != pszString) + { + memmove(pstrDestination, pszString, nRemainder); + } + + Assert(pstrNew == pstrDestination); + + // Need to set the pointer if we did were larger than capacity. + if (pstrNew != pszString) + SetPtr(pstrNew, nNewLength); + } + else // new is shorter than or same length as old, move in place + { + char *pstrNew = Access(); + char *pstrPreviousHit = pstrNew; + char *pstrDestination = pstrNew; + FOR_EACH_VEC(vecMatches, i) + { + pstrHit = vecMatches[i]; + if (pstrDestination != pstrPreviousHit) + { + // memmove very important as it is ok with overlaps. + memmove(pstrDestination, pstrPreviousHit, pstrHit - pstrPreviousHit); + } + pstrDestination += (pstrHit - pstrPreviousHit); + memcpy(pstrDestination, pstrReplacement, nReplacementLength); + pstrDestination += nReplacementLength; + pstrPreviousHit = const_cast(pstrHit)+nTargetLength; + } + + // copy trailing stuff + if (pstrDestination != pstrPreviousHit) + { + // memmove very important as it is ok with overlaps. + size_t nRemainder = (pstrNew + nOldLength) - pstrPreviousHit; + memmove(pstrDestination, pstrPreviousHit, nRemainder); + } + + Verify(PrepareBuffer(nNewLength) == pstrNew); + } + + } + } + + return vecMatches.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Indicates if the target string exists in this instance. +// The index is negative if the target string is not found, otherwise it is the index in the string. +//----------------------------------------------------------------------------- +ptrdiff_t CUtlStringBuilder::IndexOf(const char *pstrTarget) const +{ + return ::IndexOf(String(), pstrTarget); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// remove whitespace -- anything that is isspace() -- from the string +//----------------------------------------------------------------------------- +size_t CUtlStringBuilder::RemoveWhitespace() +{ + if (HasError()) + return 0; + + char *pstrDest = m_data.Access(); + size_t cRemoved = ::RemoveWhitespace(pstrDest); + + size_t nNewLength = m_data.Length() - cRemoved; + + if (cRemoved) + m_data.SetLength(nNewLength); + + Assert(pstrDest[nNewLength] == '\0'); // SetLength should have set this + + return cRemoved; +} + + +//----------------------------------------------------------------------------- +// Purpose: Allows setting the size to anything under the current +// capacity. Typically should not be used unless there was a specific +// reason to scribble on the string. Will not touch the string contents, +// but will append a NULL. Returns true if the length was changed. +//----------------------------------------------------------------------------- +bool CUtlStringBuilder::SetLength(size_t nLen) +{ + return m_data.SetLength(nLen) != NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Convert to heap string if needed, and give it away. +//----------------------------------------------------------------------------- +char *CUtlStringBuilder::TakeOwnership(size_t *pnLen, size_t *pnCapacity) +{ + size_t nLen = 0; + size_t nCapacity = 0; + char *psz = m_data.TakeOwnership(nLen, nCapacity); + + if (pnLen) + *pnLen = nLen; + + if (pnCapacity) + *pnCapacity = nCapacity; + + return psz; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// trim whitespace from front and back of string +//----------------------------------------------------------------------------- +size_t CUtlStringBuilder::TrimWhitespace() +{ + if (HasError()) + return 0; + + char *pchString = m_data.Access(); + int cChars = V_StrTrim(pchString); + + if (cChars) + m_data.SetLength(cChars); + + return cChars; +} + +//----------------------------------------------------------------------------- +// Purpose: adjust length and add null terminator, within capacity bounds +//----------------------------------------------------------------------------- +char *CUtlStringBuilder::Data::SetLength(size_t nChars) +{ + // heap/stack must be set correctly, and will not + // be changed by this routine. + if (IsHeap()) + { + if (!Heap.m_pchString || nChars > Heap.m_nCapacity) + return NULL; + Heap.m_nLength = (uint32)nChars; + Heap.m_pchString[nChars] = '\0'; + return Heap.m_pchString; + } + if (nChars > MAX_STACK_STRLEN) + return NULL; + Stack.m_szString[nChars] = '\0'; + Stack.SetBytesLeft(MAX_STACK_STRLEN - (uint8)nChars); + return Stack.m_szString; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows setting the raw pointer and taking ownership +//----------------------------------------------------------------------------- +void CUtlStringBuilder::Data::SetPtr(char *pchString, size_t nLength) +{ + // We don't care about the error state since we are totally replacing + // the string. + + // ok, length may be small enough to fit in our short buffer + // but we've already got a dynamically allocated string, so let + // it be in the heap buffer anyways. + Heap.m_pchString = pchString; + Heap.m_nCapacity = (uint32)nLength; + Heap.m_nLength = (uint32)nLength; + Heap.sentinel = STRING_TYPE_SENTINEL; + + // their buffer must have room for the null + Heap.m_pchString[nLength] = '\0'; +} + + +//----------------------------------------------------------------------------- +// Purpose: Enable the error state, moving the string to the heap if +// it isn't there. +//----------------------------------------------------------------------------- +void CUtlStringBuilder::Data::SetError(bool bEnableAssert) +{ + if (HasError()) + return; + + // This is not meant to be used as a status bit. Setting the error state should + // mean something very unexpected happened that you would want a call stack for. + // That is why this asserts unconditionally when the state is being flipped. + if (bEnableAssert) + AssertMsg(false, "Error State on string being set."); + + MoveToHeap(); + + Heap.sentinel = (STRING_TYPE_SENTINEL | STRING_TYPE_ERROR); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set string to empty state +//----------------------------------------------------------------------------- +void CUtlStringBuilder::Data::ClearError() +{ + if (HasError()) + { + Heap.sentinel = STRING_TYPE_SENTINEL; + Clear(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: If the string is on the stack, move it to the heap. +// create a null heap string if memory can't be allocated. +// Callers of this /need/ the string to be in the heap state +// when done. +//----------------------------------------------------------------------------- +bool CUtlStringBuilder::Data::MoveToHeap() +{ + bool bSuccess = true; + + if (!IsHeap()) + { + // try to recover the string at the point of failure, to help with debugging + size_t nLen = Length(); + char *pszHeapString = (char*)malloc(nLen + 1); + if (pszHeapString) + { + // get the string copy before corrupting the stack union + char *pszStackString = Access(); + memcpy(pszHeapString, pszStackString, nLen); + pszHeapString[nLen] = 0; + + Heap.m_pchString = pszHeapString; + Heap.m_nLength = (uint32)nLen; + Heap.m_nCapacity = (uint32)nLen; + Heap.sentinel = STRING_TYPE_SENTINEL; + } + else + { + Heap.m_pchString = NULL; + Heap.m_nLength = 0; + Heap.m_nCapacity = 0; + bSuccess = false; + Heap.sentinel = (STRING_TYPE_SENTINEL | STRING_TYPE_ERROR); + } + + } + + return bSuccess; +} diff --git a/r5dev/tier1/utlstring.h b/r5dev/tier1/utlstring.h new file mode 100644 index 00000000..9b791fae --- /dev/null +++ b/r5dev/tier1/utlstring.h @@ -0,0 +1,1488 @@ +//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef UTLSTRING_H +#define UTLSTRING_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utlmemory.h" +#include "tier1/strtools.h" +#include "limits.h" + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t) -1) +#endif + +//----------------------------------------------------------------------------- +// Base class, containing simple memory management +//----------------------------------------------------------------------------- +class CUtlBinaryBlock +{ +public: + CUtlBinaryBlock( int growSize = 0, int initSize = 0 ); + ~CUtlBinaryBlock() + { +#ifdef _DEBUG + m_nActualLength = 0x7BADF00D; +#else + m_nActualLength = 0; +#endif + } + + // NOTE: nInitialLength indicates how much of the buffer starts full + CUtlBinaryBlock( void* pMemory, int nSizeInBytes, int nInitialLength ); + CUtlBinaryBlock( const void* pMemory, int nSizeInBytes ); + + CUtlBinaryBlock( const CUtlBinaryBlock& src ); + CUtlBinaryBlock &operator=( const CUtlBinaryBlock &src ); + +#if VALVE_CPP11 + CUtlBinaryBlock( CUtlBinaryBlock&& src ); + CUtlBinaryBlock &operator=( CUtlBinaryBlock&& src ); +#endif + + void Get( void *pValue, int nMaxLen ) const; + void Set( const void *pValue, int nLen ); + const void *Get( ) const; + void *Get( ); + + unsigned char& operator[]( int i ); + const unsigned char& operator[]( int i ) const; + + int Length() const; + void SetLength( int nLength ); // Undefined memory will result + bool IsEmpty() const; + void Clear(); + void Purge(); + + bool IsReadOnly() const; + + + // Test for equality + bool operator==( const CUtlBinaryBlock &src ) const; + +private: + CUtlMemory m_Memory; + int m_nActualLength; +}; + + +//----------------------------------------------------------------------------- +// class inlines +//----------------------------------------------------------------------------- + +#if VALVE_CPP11 +inline CUtlBinaryBlock::CUtlBinaryBlock( CUtlBinaryBlock&& src ) +: m_Memory( Move(src.m_Memory) ) +, m_nActualLength( src.m_nActualLength ) +{ + src.m_nActualLength = 0; +} + +inline CUtlBinaryBlock& CUtlBinaryBlock::operator= ( CUtlBinaryBlock&& src ) +{ + int length = src.m_nActualLength; + src.m_nActualLength = 0; + + m_Memory = Move( src.m_Memory ); + m_nActualLength = length; + + return *this; +} +#endif + +inline const void *CUtlBinaryBlock::Get( ) const +{ + return m_Memory.Base(); +} + +inline void *CUtlBinaryBlock::Get( ) +{ + return m_Memory.Base(); +} + +inline int CUtlBinaryBlock::Length() const +{ + return m_nActualLength; +} + +inline unsigned char& CUtlBinaryBlock::operator[]( int i ) +{ + return m_Memory[i]; +} + +inline const unsigned char& CUtlBinaryBlock::operator[]( int i ) const +{ + return m_Memory[i]; +} + +inline bool CUtlBinaryBlock::IsReadOnly() const +{ + return m_Memory.IsReadOnly(); +} + +inline bool CUtlBinaryBlock::IsEmpty() const +{ + return Length() == 0; +} + +inline void CUtlBinaryBlock::Clear() +{ + SetLength( 0 ); +} + +inline void CUtlBinaryBlock::Purge() +{ + SetLength( 0 ); + m_Memory.Purge(); +} + + +//----------------------------------------------------------------------------- +// Simple string class. +// NOTE: This is *not* optimal! Use in tools, but not runtime code +//----------------------------------------------------------------------------- +class CUtlString +{ +public: + CUtlString(); // = default; + CUtlString( const char *pString ); // initialize from c-style string + + // Attaches the string to external memory. Useful for avoiding a copy + CUtlString( void* pMemory, int nSizeInBytes, int nInitialLength ); + CUtlString( const void* pMemory, int nSizeInBytes ); + + // Copy/move constructor/assignment + // Moves are extremely efficient as the underlying memory is not copied, just the pointers. + CUtlString( const CUtlString& string ); // = default; + CUtlString &operator=( const CUtlString &src ); // = default; + +#if VALVE_CPP11 + CUtlString( CUtlString&& moveFrom ); // = default; + CUtlString &operator=( CUtlString&& moveFrom ); // = default; +#endif + + // Also can assign from a regular C-style string + CUtlString &operator=( const char *src ); + + char *Access() { return Get(); } + const char *Get( ) const; + char *Get(); + char* GetForModify(); + + void Clear() { Set( NULL ); } + + // CUtlString can be used anywhere a c-style string would be required + // via this implicit conversion + operator const char*() const; + + // for compatibility switching items from UtlSymbol + const char *String() const { return Get(); } + + // Returns strlen + int Length() const; + bool IsEmpty() const; + + // GS - Added for chromehtml + bool IsValid() + { + return (Length() != 0); + } + + // Sets the length (used to serialize into the buffer ) + // Note: If nLen != 0, then this adds an extra byte for a null-terminator. + void Set( const char *pValue ); + void SetLength( int nLen ); + void Purge(); + + void Swap(CUtlString &src); + + // Case Change + void ToUpper(); + void ToLower( ); + void Append( const char *pchAddition ); + void Append(const char *pchAddition, int nMaxChars); + void Append(char chAddition) { + char temp[2] = { chAddition, 0 }; + Append(temp); + } + + // Strips the trailing slash + void StripTrailingSlash(); + + char operator[] ( int idx ) const; + char& operator[] ( int idx ); + + // Test for equality + bool operator==( const CUtlString &src ) const; + bool operator==( const char *src ) const; + bool operator!=( const CUtlString &src ) const { return !operator==( src ); } + bool operator!=( const char *src ) const { return !operator==( src ); } + + // If these are not defined, CUtlString as rhs will auto-convert + // to const char* and do logical operations on the raw pointers. Ugh. + inline friend bool operator==( const char *lhs, const CUtlString &rhs ) { return rhs.operator==( lhs ); } + inline friend bool operator!=( const char *lhs, const CUtlString &rhs ) { return rhs.operator!=( lhs ); } + + CUtlString &operator+=( const CUtlString &rhs ); + CUtlString &operator+=( const char *rhs ); + CUtlString &operator+=( char c ); + CUtlString &operator+=( int rhs ); + CUtlString &operator+=( double rhs ); + + CUtlString operator+( const char *pOther )const; + CUtlString operator+( int rhs )const; + + bool MatchesPattern( const CUtlString &Pattern, int nFlags = 0 ); // case SENSITIVE, use * for wildcard in pattern string + +#if ! defined(SWIG) + // Don't let SWIG see the PRINTF_FORMAT_STRING attribute or it will complain. + int Format( PRINTF_FORMAT_STRING const char *pFormat, ... ) FMTFUNCTION( 2, 3 ); + int FormatV( PRINTF_FORMAT_STRING const char *pFormat, va_list marker ); +#else + int Format( const char *pFormat, ... ); + int FormatV( const char *pFormat, va_list marker ); +#endif + + void SetDirect( const char *pValue, int nChars ); + + // Defining AltArgumentType_t hints that associative container classes should + // also implement Find/Insert/Remove functions that take const char* params. + typedef const char *AltArgumentType_t; + + // Take a piece out of the string. + // If you only specify nStart, it'll go from nStart to the end. + // You can use negative numbers and it'll wrap around to the start. + CUtlString Slice( int32 nStart=0, int32 nEnd=INT_MAX ); + + // Grab a substring starting from the left or the right side. + CUtlString Left( int32 nChars ); + CUtlString Right( int32 nChars ); + + CUtlString Remove(char const *pTextToRemove, bool bCaseSensitive) const; + + // Replace all instances of one character with another. + CUtlString Replace( char cFrom, char cTo ); + + /// Replace one string with the other (single pass). Passing a NULL to pchTo is same as calling Remove + CUtlString Replace( char const *pchFrom, const char *pchTo, bool bCaseSensitive = false ) const; + + /// helper func for caseless replace + CUtlString ReplaceCaseless( char const *pchFrom, const char *pchTo ) const { return Replace( pchFrom, pchTo, false ); } + + void RemoveDotSlashes(char separator = CORRECT_PATH_SEPARATOR); + + // Trim whitespace + void TrimLeft( const char *szTargets = "\t\r\n " ); + void TrimRight( const char *szTargets = "\t\r\n " ); + void Trim( const char *szTargets = "\t\r\n " ); + + // Calls right through to V_MakeAbsolutePath. + CUtlString AbsPath( const char *pStartingDir=NULL ) const; + + CUtlString AbsPath(const char *pStartingDir, bool bLowercaseName) const + { + CUtlString result = AbsPath(pStartingDir); + if (bLowercaseName) + { + result.ToLower(); + } + return result; + } + + // Gets the filename (everything except the path.. c:\a\b\c\somefile.txt -> somefile.txt). + CUtlString UnqualifiedFilename() const; + + // Strips off one directory. Uses V_StripLastDir but strips the last slash also! + CUtlString DirName(); + + // Get a string with the extension removed (with V_StripExtension). + CUtlString StripExtension() const; + + // Get a string with the filename removed (uses V_UnqualifiedFileName and also strips the last slash) + CUtlString StripFilename() const; + + // Get a string with the base filename (with V_FileBase). + CUtlString GetBaseFilename() const; + + // Get a string with the file extension (with V_FileBase). + CUtlString GetExtension() const; + + // Works like V_ComposeFileName. + static CUtlString PathJoin( const char *pStr1, const char *pStr2 ); + + // These can be used for utlvector sorts. + static int __cdecl SortCaseInsensitive( const CUtlString *pString1, const CUtlString *pString2 ); + static int __cdecl SortCaseSensitive( const CUtlString *pString1, const CUtlString *pString2 ); + + // From Src2 + + void FixSlashes( char cSeparator = CORRECT_PATH_SEPARATOR ) + { + for ( int nLength = Length() - 1; nLength >= 0; nLength-- ) + { + char *pname = (char*)&m_Storage[ nLength ]; + if ( *pname == INCORRECT_PATH_SEPARATOR || *pname == CORRECT_PATH_SEPARATOR ) + { + *pname = cSeparator; + } + } + } + + bool IsEqual_CaseSensitive( const char *src ) const + { + if ( !src ) + { + return ( Length() == 0 ); + } + return ( V_strcmp( Get(), src ) == 0 ); + } + + bool IsEqual_CaseInsensitive(const char *src) const + { + if (!src) + { + return (Length() == 0); + } + return (V_stricmp(Get(), src) == 0); + } + + +private: + CUtlBinaryBlock m_Storage; +}; + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- + +#if VALVE_CPP11 +inline CUtlString::CUtlString( CUtlString&& moveFrom ) +: m_Storage( Move( moveFrom.m_Storage ) ) +{ +} + +inline CUtlString& CUtlString::operator=( CUtlString&& moveFrom ) +{ + m_Storage = Move( moveFrom.m_Storage ); + return *this; +} +#endif + +inline bool CUtlString::IsEmpty() const +{ + return Length() == 0; +} + +inline int __cdecl CUtlString::SortCaseInsensitive( const CUtlString *pString1, const CUtlString *pString2 ) +{ + return V_stricmp( pString1->String(), pString2->String() ); +} + +inline int __cdecl CUtlString::SortCaseSensitive( const CUtlString *pString1, const CUtlString *pString2 ) +{ + return V_strcmp( pString1->String(), pString2->String() ); +} + +inline char CUtlString::operator [] ( int index ) const +{ + return Get()[index]; +} + +inline char& CUtlString::operator[] ( int index ) +{ + return Access()[index]; +} + +inline CUtlString::operator const char *( ) const +{ + return Get(); +} + +//----------------------------------------------------------------------------- +// Purpose: Implementation of low-level string functionality for character types. +//----------------------------------------------------------------------------- + +template < typename T > +class StringFuncs +{ +public: + static T *Duplicate( const T *pValue ); + static void Copy( T *out_pOut, const T *pIn, int iLength ); + static int Compare( const T *pLhs, const T *pRhs ); + static int Length( const T *pValue ); + static const T *FindChar( const T *pStr, const T cSearch ); + static const T *EmptyString(); +}; + +template < > +class StringFuncs +{ +public: + static char *Duplicate( const char *pValue ) { return strdup( pValue ); } + static void Copy( char *out_pOut, const char *pIn, int iLength ) { strncpy( out_pOut, pIn, iLength ); } + static int Compare( const char *pLhs, const char *pRhs ) { return strcmp( pLhs, pRhs ); } + static int Length( const char *pValue ) { return (int)strlen( pValue ); } + static const char *FindChar( const char *pStr, const char cSearch ) { return strchr( pStr, cSearch ); } + static const char *EmptyString() { return ""; } +}; + +template < > +class StringFuncs +{ +public: + static wchar_t *Duplicate( const wchar_t *pValue ) { return wcsdup( pValue ); } + static void Copy( wchar_t *out_pOut, const wchar_t *pIn, int iLength ) { wcsncpy( out_pOut, pIn, iLength ); } + static int Compare( const wchar_t *pLhs, const wchar_t *pRhs ) { return wcscmp( pLhs, pRhs ); } + static int Length( const wchar_t *pValue ) { return (int) wcslen( pValue ); } + static const wchar_t *FindChar( const wchar_t *pStr, const wchar_t cSearch ) { return wcschr( pStr, cSearch ); } + static const wchar_t *EmptyString() { return L""; } +}; + +//----------------------------------------------------------------------------- +// Dirt-basic auto-release string class. Not intended for manipulation, +// can be stored in a container or forwarded as a functor parameter. +// Note the benefit over CUtlString: sizeof(CUtlConstString) == sizeof(char*). +// Also note: null char* pointers are treated identically to empty strings. +//----------------------------------------------------------------------------- + +template < typename T = char > +class CUtlConstStringBase +{ +public: + CUtlConstStringBase() : m_pString( NULL ) {} + CUtlConstStringBase( const T *pString ) : m_pString( NULL ) { Set( pString ); } + CUtlConstStringBase( const CUtlConstStringBase& src ) : m_pString( NULL ) { Set( src.m_pString ); } + ~CUtlConstStringBase() { Set( NULL ); } + + void Set( const T *pValue ); + void Clear() { Set( NULL ); } + + const T *Get() const { return m_pString ? m_pString : StringFuncs::EmptyString(); } + operator const T*() const { return m_pString ? m_pString : StringFuncs::EmptyString(); } + + bool IsEmpty() const { return m_pString == NULL; } // Note: empty strings are never stored by Set + + int Compare( const T *rhs ) const; + + // Logical ops + bool operator<( const T *rhs ) const { return Compare( rhs ) < 0; } + bool operator==( const T *rhs ) const { return Compare( rhs ) == 0; } + bool operator!=( const T *rhs ) const { return Compare( rhs ) != 0; } + bool operator<( const CUtlConstStringBase &rhs ) const { return Compare( rhs.m_pString ) < 0; } + bool operator==( const CUtlConstStringBase &rhs ) const { return Compare( rhs.m_pString ) == 0; } + bool operator!=( const CUtlConstStringBase &rhs ) const { return Compare( rhs.m_pString ) != 0; } + + // If these are not defined, CUtlConstString as rhs will auto-convert + // to const char* and do logical operations on the raw pointers. Ugh. + inline friend bool operator<( const T *lhs, const CUtlConstStringBase &rhs ) { return rhs.Compare( lhs ) > 0; } + inline friend bool operator==( const T *lhs, const CUtlConstStringBase &rhs ) { return rhs.Compare( lhs ) == 0; } + inline friend bool operator!=( const T *lhs, const CUtlConstStringBase &rhs ) { return rhs.Compare( lhs ) != 0; } + + CUtlConstStringBase &operator=( const T *src ) { Set( src ); return *this; } + CUtlConstStringBase &operator=( const CUtlConstStringBase &src ) { Set( src.m_pString ); return *this; } + + // Defining AltArgumentType_t is a hint to containers that they should + // implement Find/Insert/Remove functions that take const char* params. + typedef const T *AltArgumentType_t; + +protected: + const T *m_pString; +}; + +template < typename T > +void CUtlConstStringBase::Set( const T *pValue ) +{ + if ( pValue != m_pString ) + { + free( ( void* ) m_pString ); + m_pString = pValue && pValue[0] ? StringFuncs::Duplicate( pValue ) : NULL; + } +} + +template < typename T > +int CUtlConstStringBase::Compare( const T *rhs ) const +{ + if ( m_pString ) + { + if ( rhs ) + return StringFuncs::Compare( m_pString, rhs ); + else + return 1; + } + else + { + if ( rhs ) + return -1; + else + return 0; + } +} + +inline CUtlString CUtlString::operator+( const char *pOther )const +{ + CUtlString s = *this; + s += pOther; + return s; +} + + + +typedef CUtlConstStringBase CUtlConstString; +typedef CUtlConstStringBase CUtlConstWideString; + +//----------------------------------------------------------------------------- +// Purpose: General purpose string class good for when it +// is rarely expected to be empty, and/or will undergo +// many modifications/appends. +//----------------------------------------------------------------------------- +class CUtlStringBuilder +{ +public: + CUtlStringBuilder(); + CUtlStringBuilder(const char *pchString); + CUtlStringBuilder(CUtlStringBuilder const &string); + explicit CUtlStringBuilder(size_t nPreallocateBytes); + //CUtlStringBuilder( const CUtlStringResult &strMoveSource ); + //explicit CUtlStringBuilder( IFillStringFunctor& func ); + ~CUtlStringBuilder(); + + // operator= + CUtlStringBuilder &operator=(const CUtlStringBuilder &src); + CUtlStringBuilder &operator=(const char *pchString); + //CUtlStringBuilder &operator=( const CUtlStringResult &strMoveSource ); + + // operator== + bool operator==(CUtlStringBuilder const &src) const; + bool operator==(const char *pchString) const; + + // operator!= + bool operator!=(CUtlStringBuilder const &src) const; + bool operator!=(const char *pchString) const; + + // operator , performs case sensitive comparison + bool operator<(const CUtlStringBuilder &val) const; + bool operator<(const char *pchString) const; + bool operator>(const CUtlStringBuilder &val) const; + bool operator>(const char *pchString) const; + + // operator+= + CUtlStringBuilder &operator+=(const char *rhs); + + // is valid? + bool IsValid() const; + + // gets the string + // never returns NULL, use IsValid() to see if it's never been set + const char *String() const; + const char *Get() const { return String(); } + operator const char *() const { return String(); } + + // returns the string directly (could be NULL) + // useful for doing inline operations on the string + char *Access() + { + // aggressive warning that there has been a bad error; + // probably should not be retrieving the string by pointer. + Assert(!m_data.HasError()); + + if (!IsValid() || (Capacity() == 0)) + return NULL; + + return m_data.Access(); + } + + // return false if capacity can't be set. If false is returned, + // the error state is set. + bool EnsureCapacity(size_t nLength); + + // append in-place, causing a re-allocation + void Append(const char *pchAddition); + void Append(const char *pchAddition, size_t cbLen); + void Append(const CUtlStringBuilder &str) { Append(str.String(), str.Length()); } + //void Append( IFillStringFunctor& func ); + void AppendChar(char ch) { Append(&ch, 1); } + void AppendRepeat(char ch, int cCount); + + // sets the string + void SetValue(const char *pchString); + void Set(const char *pchString); + void Clear() { m_data.Clear(); } + void SetPtr(char *pchString); + void SetPtr(char *pchString, size_t nLength); + void Swap(CUtlStringBuilder &src); + + // If you want to take ownership of the ptr, you can use this. So for instance if you had + // a CUtlString you wanted to move it to a CUtlStringConst without making a copy, you + // could do: CUtlStringConst strConst( strUtl.Detach() ); + // Also used for fast temporaries when a string that does not need to be retained is being + // returned from a func. ie: return (str.Detach()); + // All strings in this file can take a CUtlStringResult as a constructor parm and will take ownership directly. + //CUtlStringResult Detach(); + + // Set directly and don't look for a null terminator in pValue. + // nChars is the string length. "abcd" nChars==3 would copy and null + // terminate "abc" in the string object. + void SetDirect(const char *pchString, size_t nChars); + + // Get the length of the string in characters. + size_t Length() const; + bool IsEmpty() const; + size_t Capacity() const { return m_data.Capacity(); } // how much room is there to scribble + +#if !defined(SWIG) + // SWIG fails on PRINTF_FORMAT_STRING + // Format like sprintf. + size_t Format(PRINTF_FORMAT_STRING const char *pFormat, ...) FMTFUNCTION(2, 3); + // format, then append what we crated in the format + size_t AppendFormat(PRINTF_FORMAT_STRING const char *pFormat, ...) FMTFUNCTION(2, 3); +#else + size_t Format(const char *pFormat, ...); + size_t AppendFormat(const char *pFormat, ...); +#endif + + // replace a single character with another, returns hit count + size_t Replace(char chTarget, char chReplacement); + + // replace a string with another string, returns hit count + // replacement string might be NULL or "" to remove target substring + size_t Replace(const char *pstrTarget, const char *pstrReplacement); + // ASCII-only case-insensitivity. + size_t ReplaceFastCaseless(const char *pstrTarget, const char *pstrReplacement); + + // replace a sequence of characters at a given point with a new string + // replacement string might be NULL or "" to remove target substring + bool ReplaceAt(size_t nIndex, size_t nOldChars, const char *pNewStr, size_t nNewChars = SIZE_MAX); + + ptrdiff_t IndexOf(const char *pstrTarget) const; + + // remove whitespace from the string; anything that is isspace() + size_t RemoveWhitespace(); + + // trim whitepace from the beginning and end of the string + size_t TrimWhitespace(); + + // Allows setting the size to anything under the current + // capacity. Typically should not be used unless there was a specific + // reason to scribble on the string. Will not touch the string contents, + // but will append a NULL. Returns true if the length was changed. + bool SetLength(size_t nLen); + + // Take responsibility for the string. May cause a heap alloc. + char *TakeOwnership(size_t *pnLen, size_t *pnCapacity); + + // For operations that are long and/or complex - if something fails + // along the way, the error will be set and can be queried at the end. + // The string is undefined in the error state, but will likely hold the + // last value before the error occurred. The string is cleared + // if ClearError() is called. The error can be set be the user, and it + // will also be set if a dynamic allocation fails in string operations + // where it needs to grow the capacity. + void SetError() { m_data.SetError(true); } + void ClearError() { m_data.ClearError(); } + bool HasError() const { return m_data.HasError(); } + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, const char *pchName); // validate our internal structures +#endif // DBGFLAG_VALIDATE + + size_t VFormat(const char *pFormat, va_list args); + size_t VAppendFormat(const char *pFormat, va_list args); + void Truncate(size_t nChars); + + // Access() With no assertion check - should only be used for tests + char *AccessNoAssert() + { + if (!IsValid()) + return NULL; + return m_data.Access(); + } + + // SetError() With no assertion check - should only be used for tests + void SetErrorNoAssert() { m_data.SetError(false); } + + size_t ReplaceCaseless(const char *pstrTarget, const char *pstrReplacement) { + return ReplaceFastCaseless(pstrTarget, pstrReplacement); + } + +private: + size_t ReplaceInternal(const char *pstrTarget, const char *pstrReplacement, const char *pfnCompare(const char*, const char*)); + operator bool() const { return IsValid(); } + + // nChars is the number of characters you want, NOT including the null + char *PrepareBuffer(size_t nChars, bool bCopyOld = false, size_t nMinCapacity = 0) + { + char *pszString = NULL; + size_t nCapacity = m_data.Capacity(); + if ((nChars <= nCapacity) && (nMinCapacity <= nCapacity)) + { + // early out leaving it all alone, just update the length, + // even if it shortens an existing heap string to a width + // that would fit in the stack buffer. + pszString = m_data.SetLength(nChars); + + // SetLength will have added the null. Pointer might + // be NULL if there is an error state and no buffer + Assert(!pszString || pszString[nChars] == '\0'); + + return pszString; + } + + if (HasError()) + return NULL; + + // Need to actually adjust the capacity + return InternalPrepareBuffer(nChars, bCopyOld, Max(nChars, nMinCapacity)); + } + + char *InternalPrepareBuffer(size_t nChars, bool bCopyOld, size_t nMinCapacity); + + template + void Swap(T &p1, T &p2) + { + T t = p1; + p1 = p2; + p2 = t; + } + + // correct for 32 or 64 bit + static const uint8 MAX_STACK_STRLEN = (sizeof(void*) == 4 ? 15 : 19); + enum + { + // Note: If it's ever desired to have the embedded string be larger than + // 63 (0x40-1), just make the sentinal 0xFF, and the error something (0xFF also + // is fine). Then shrink the scrap size by 1 and add a uint8 for the error state. + // The error byte is only valid if the heap is on (already have that restriction). + // and then embedded strings can get back to being up to 254. It's not done this + // way now just to make the tests for IsHeap()/HasError() faster since they + // are often both tested together the compiler can do nice bit test optimizations. + STRING_TYPE_SENTINEL = 0x80, + STRING_TYPE_ERROR = 0x40 + }; // if Data.Stack.BytesLeft() or Data.Heap.sentinel == this value, data is in heap + + union Data + { + struct _Heap + { // 16 on 32 bit, sentinel == 0xff if Heap is the active union item + private: + char *m_pchString; + uint32 m_nLength; + uint32 m_nCapacity; // without trailing null; ie: m_pchString[m_nCapacity] = '\0' is not out of bounds + uint8 scrap[3]; + uint8 sentinel; + public: + friend union Data; + friend char *CUtlStringBuilder::InternalPrepareBuffer(size_t, bool, size_t); + } Heap; + + struct _Stack + { + private: + // last byte is doing a hack double duty. It holds how many bytes + // are left in the string; so when the string is 'full' it will be + // '0' and thus suffice as the terminating null. This is why + // we hold remaining chars instead of 'string length' + char m_szString[MAX_STACK_STRLEN + 1]; + public: + uint8 BytesLeft() const { return (uint8)(m_szString[MAX_STACK_STRLEN]); } + void SetBytesLeft(char n) { m_szString[MAX_STACK_STRLEN] = n; } + + friend char *CUtlStringBuilder::InternalPrepareBuffer(size_t, bool, size_t); + friend union Data; + } Stack; + + // set to a clear state without looking at the current state + void Construct() + { + Stack.m_szString[0] = '\0'; + Stack.SetBytesLeft(MAX_STACK_STRLEN); + } + + // If we have heap allocated data, free it + void FreeHeap() + { + if (IsHeap() && Heap.m_pchString) + MemAlloc_Free(Heap.m_pchString); + } + + // Back to a clean state, but retain the error state. + void Clear() + { + if (HasError()) + return; + + FreeHeap(); + Heap.m_pchString = NULL; + + Construct(); + } + + bool IsHeap() const { return ((Heap.sentinel & STRING_TYPE_SENTINEL) != 0); } + + char *Access() { return IsHeap() ? Heap.m_pchString : Stack.m_szString; } + const char *String() const { return IsHeap() ? Heap.m_pchString : Stack.m_szString; } + + size_t Length() const { return IsHeap() ? Heap.m_nLength : (MAX_STACK_STRLEN - Stack.BytesLeft()); } + bool IsEmpty() const + { + if (IsHeap()) + return Heap.m_nLength == 0; + else + return Stack.BytesLeft() == MAX_STACK_STRLEN; // empty if all the bytes are available + } + + size_t Capacity() const { return IsHeap() ? Heap.m_nCapacity : MAX_STACK_STRLEN; } + + // Internally the code often needs the char * after setting the length, so + // just return it from here for conveniences. + char *SetLength(size_t nChars); + + // Give the string away and set to an empty state + char *TakeOwnership(size_t &nLen, size_t &nCapacity) + { + MoveToHeap(); + + if (HasError()) + { + nLen = 0; + nCapacity = 0; + return NULL; + } + + nLen = Heap.m_nLength; + nCapacity = Heap.m_nCapacity; + char *psz = Heap.m_pchString; + Construct(); + return psz; + } + + void SetPtr(char *pchString, size_t nLength); + + // Set the string to an error state + void SetError(bool bEnableAssert); + + // clear the error state and reset the string + void ClearError(); + + // If string is in the heap and the error bit is set in the sentinel + // the error state is true. + bool HasError() const { return IsHeap() && ((Heap.sentinel & STRING_TYPE_ERROR) != 0); } + + // If it's stack based, get it to the heap and return if it is + // successfully on the heap (or already was) + bool MoveToHeap(); + + private: + //----------------------------------------------------------------------------- + // Purpose: Needed facts for string class to work + //----------------------------------------------------------------------------- + void StaticAssertTests() + { + // If this fails when the heap sentinel and where the stack string stores its bytes left + // aren't aliases. This is needed so that regardless of how the 'sentinel' to mark + // that the string is on the heap is set, it is set as expected on both sides of the union. + COMPILE_TIME_ASSERT(offsetof(_Heap, sentinel) == (offsetof(_Stack, m_szString) + MAX_STACK_STRLEN)); + + // Lots of code assumes it can look at m_data.Stack.m_nBytesLeft for an empty string; which + // means that it will equal MAX_STACK_STRLEN. Therefor it must be a different value than + // the STRING_TYPE_SENTINEL which will be set if the string is in the heap. + COMPILE_TIME_ASSERT(MAX_STACK_STRLEN < STRING_TYPE_SENTINEL); + COMPILE_TIME_ASSERT(MAX_STACK_STRLEN < STRING_TYPE_ERROR); + + // this is a no brainer, and I don't know anywhere in the world this isn't true, + // but this code does take this dependency. + COMPILE_TIME_ASSERT(0 == '\0'); + } + }; + +private: // data + Data m_data; + +}; + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +inline CUtlStringBuilder::CUtlStringBuilder() +{ + m_data.Construct(); +} + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +inline CUtlStringBuilder::CUtlStringBuilder(size_t nPreallocateBytes) +{ + if (nPreallocateBytes <= MAX_STACK_STRLEN) + { + m_data.Construct(); + } + else + { + m_data.Construct(); + PrepareBuffer(0, false, nPreallocateBytes); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +inline CUtlStringBuilder::CUtlStringBuilder(const char *pchString) +{ + m_data.Construct(); + SetDirect(pchString, pchString ? strlen(pchString) : 0); +} + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +inline CUtlStringBuilder::CUtlStringBuilder(CUtlStringBuilder const &string) +{ + m_data.Construct(); + SetDirect(string.String(), string.Length()); + + // attempt the copy before checking for error. On the off chance there + // is data there that can be set, it will help with debugging. + if (string.HasError()) + m_data.SetError(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: destructor +//----------------------------------------------------------------------------- +inline CUtlStringBuilder::~CUtlStringBuilder() +{ + m_data.FreeHeap(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Pre-Widen a string to an expected length +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::EnsureCapacity(size_t nLength) +{ + return PrepareBuffer(Length(), true, nLength) != NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: ask if the string has anything in it +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::IsEmpty() const +{ + return m_data.IsEmpty(); +} + +//----------------------------------------------------------------------------- +// Purpose: assignment +//----------------------------------------------------------------------------- +inline CUtlStringBuilder &CUtlStringBuilder::operator=(const char *pchString) +{ + SetDirect(pchString, pchString ? strlen(pchString) : 0); + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: assignment +//----------------------------------------------------------------------------- +inline CUtlStringBuilder &CUtlStringBuilder::operator=(CUtlStringBuilder const &src) +{ + if (&src != this) + { + SetDirect(src.String(), src.Length()); + // error propagate + if (src.HasError()) + m_data.SetError(false); + } + + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator==(CUtlStringBuilder const &src) const +{ + return !Q_strcmp(String(), src.String()); +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator==(const char *pchString) const +{ + return !Q_strcmp(String(), pchString); +} + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator!=(CUtlStringBuilder const &src) const +{ + return !(*this == src); +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator!=(const char *pchString) const +{ + return !(*this == pchString); +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator<(CUtlStringBuilder const &val) const +{ + return operator<(val.String()); +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator<(const char *pchString) const +{ + return Q_strcmp(String(), pchString) < 0; +} + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator>(CUtlStringBuilder const &val) const +{ + return Q_strcmp(String(), val.String()) > 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: comparison +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::operator>(const char *pchString) const +{ + return Q_strcmp(String(), pchString) > 0; +} + + +//----------------------------------------------------------------------------- +// Return a string with this string and rhs joined together. +inline CUtlStringBuilder& CUtlStringBuilder::operator+=(const char *rhs) +{ + Append(rhs); + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the string is not null +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::IsValid() const +{ + return !HasError(); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +inline const char *CUtlStringBuilder::String() const +{ + const char *pszString = m_data.String(); + if (pszString) + return pszString; + + // pszString can be NULL in the error state. For const char* + // never return NULL. + return ""; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the string to be the new value, taking a copy of it +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::SetValue(const char *pchString) +{ + size_t nLen = (pchString ? strlen(pchString) : 0); + SetDirect(pchString, nLen); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set directly and don't look for a null terminator in pValue. +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::SetDirect(const char *pchSource, size_t nChars) +{ + if (HasError()) + return; + + if (m_data.IsHeap() && Get() == pchSource) + return; + + if (!pchSource || !nChars) + { + m_data.Clear(); + return; + } + + char *pszString = PrepareBuffer(nChars); + + if (pszString) + { + memcpy(pszString, pchSource, nChars); + // PrepareBuffer already allocated space for the terminating null, + // and inserted it for us. Make sure we didn't clobber it. + // Also assign it anyways so we don't risk the caller having a buffer + // running into random bytes. +#ifdef _DEBUG + // Suppress a bogus noisy warning: + // warning C6385: Invalid data: accessing 'pszString', the readable size is 'nChars' bytes, but '1001' bytes might be read + ANALYZE_SUPPRESS(6385); + Assert(pszString[nChars] == '\0'); + pszString[nChars] = '\0'; +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the string to be the new value, taking a copy of it +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::Set(const char *pchString) +{ + SetValue(pchString); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the string to be the new value, taking ownership of the pointer +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::SetPtr(char *pchString) +{ + size_t nLength = pchString ? strlen(pchString) : 0; + SetPtr(pchString, nLength); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the string to be the new value, taking ownership of the pointer +// This API will clear the error state if it was set. +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::SetPtr(char *pchString, size_t nLength) +{ + m_data.Clear(); + + if (!pchString || !nLength) + { + if (pchString) + MemAlloc_Free(pchString); // we don't hang onto empty strings. + return; + } + + m_data.SetPtr(pchString, nLength); +} + +//----------------------------------------------------------------------------- +// Purpose: return the conceptual 'strlen' of the string. +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::Length() const +{ + return m_data.Length(); +} + + +//----------------------------------------------------------------------------- +// Purpose: format something sprintf() style, and take it as the new value of this CUtlStringBuilder +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::Format(const char *pFormat, ...) +{ + va_list args; + va_start(args, pFormat); + size_t nLen = VFormat(pFormat, args); + va_end(args); + return nLen; +} + +int V_vscprintf(const char *format, va_list argptr); + +//----------------------------------------------------------------------------- +// Purpose: Helper for Format() method +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::VFormat(const char *pFormat, va_list args) +{ + if (HasError()) + return 0; + + int len = 0; +#ifdef _WIN32 + // how much space will we need? + len = V_vscprintf(pFormat, args); +#else + // ISO spec defines the NULL/0 case as being valid and will return the + // needed length. Verified on PS3 as well. Ignore that bsd/linux/mac + // have vasprintf which will allocate a buffer. We'd rather have the + // self growing buffer management ourselves. Even the best implementations + // There does not seem to be a magic vasprintf that is significantly + // faster than 2 passes (some guess and get lucky). + + // Scope ReuseArgs. + { + CReuseVaList ReuseArgs(args); + len = V_vsnprintf(NULL, 0, pFormat, ReuseArgs.m_ReuseList); + } +#endif + if (len > 0) + { + // get it + char *pszString = PrepareBuffer(len, true); + if (pszString) + len = V_vsnprintf(pszString, len + 1, pFormat, args); + else + len = 0; + } + + Assert(len > 0 || HasError()); + return len; +} + + +//----------------------------------------------------------------------------- +// format a string and append the result to the string we hold +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::AppendFormat(const char *pFormat, ...) +{ + va_list args; + va_start(args, pFormat); + size_t nLen = VAppendFormat(pFormat, args); + va_end(args); + return nLen; +} + + + +//----------------------------------------------------------------------------- +// Purpose: implementation helper for AppendFormat() +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::VAppendFormat(const char *pFormat, va_list args) +{ + if (HasError()) + return 0; + + int len = 0; +#ifdef _WIN32 + // how much space will we need? + len = _vscprintf(pFormat, args); +#else + + // ISO spec defines the NULL/0 case as being valid and will return the + // needed length. Verified on PS3 as well. Ignore that bsd/linux/mac + // have vasprintf which will allocate a buffer. We'd rather have the + // self growing buffer management ourselves. Even the best implementations + // There does not seem to be a magic vasprintf that is significantly + // faster than 2 passes (some guess and get lucky). + + // Scope ReuseArgs. + { + CReuseVaList ReuseArgs(args); + len = vsnprintf(NULL, 0, pFormat, ReuseArgs.m_ReuseList); + } +#endif + size_t nOldLen = Length(); + + if (len > 0) + { + // get it + + char *pszString = PrepareBuffer(nOldLen + len, true); + if (pszString) + len = _vsnprintf(&pszString[nOldLen], len + 1, pFormat, args); + else + len = 0; + } + + Assert(len > 0 || HasError()); + return nOldLen + len; +} + +//----------------------------------------------------------------------------- +// Purpose: concatenate the provided string to our current content +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::Append(const char *pchAddition) +{ + if (pchAddition && pchAddition[0]) + { + size_t cchLen = strlen(pchAddition); + Append(pchAddition, cchLen); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: concatenate the provided string to our current content +// when the additional string length is known +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::Append(const char *pchAddition, size_t cbLen) +{ + if (pchAddition && pchAddition[0] && cbLen) + { + if (IsEmpty()) + { + SetDirect(pchAddition, cbLen); + } + else + { + size_t cbOld = Length(); + char *pstrNew = PrepareBuffer(cbOld + cbLen, true); + + // make sure we use raw memcpy to get intrinsic + if (pstrNew) + memcpy(pstrNew + cbOld, pchAddition, cbLen); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: append a repeated series of a single character +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::AppendRepeat(char ch, int cCount) +{ + size_t cbOld = Length(); + char *pstrNew = PrepareBuffer(cbOld + cCount, true); + if (pstrNew) + memset(pstrNew + cbOld, ch, cCount); +} + +//----------------------------------------------------------------------------- +// Purpose: Swaps string contents +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::Swap(CUtlStringBuilder &src) +{ + // swapping m_data instead of '*this' prevents having to + // copy dynamic strings. Important that m_data doesn't know + // any lifetime rules about its members (ie: it should not have + // a destructor that frees the dynamic string pointer). + Swap(m_data, src.m_data); +} + + +//----------------------------------------------------------------------------- +// Purpose: replace all occurrences of one character with another +//----------------------------------------------------------------------------- +inline size_t CUtlStringBuilder::Replace(char chTarget, char chReplacement) +{ + size_t cReplacements = 0; + + if (!IsEmpty() && !HasError()) + { + char *pszString = Access(); + for (char *pstrWalker = pszString; *pstrWalker != 0; pstrWalker++) + { + if (*pstrWalker == chTarget) + { + *pstrWalker = chReplacement; + cReplacements++; + } + } + } + + return cReplacements; +} + + +//----------------------------------------------------------------------------- +// replace a sequence of characters at a given point with a new string +// replacement string might be NULL or "" to remove target substring +//----------------------------------------------------------------------------- +inline bool CUtlStringBuilder::ReplaceAt(size_t nIndex, size_t nOldChars, const char *pNewStr, size_t nNewChars) +{ + Assert(nIndex < Length() && nIndex + nOldChars <= Length()); + + if (nNewChars == SIZE_MAX) + { + nNewChars = pNewStr ? V_strlen(pNewStr) : 0; + } + + size_t nOldLength = Length(); + ptrdiff_t nDelta = nNewChars - nOldChars; + if (nDelta < 0) + { + char *pBuf = Access(); + memmove(pBuf + nIndex + nNewChars, pBuf + nIndex + nOldChars, nOldLength - nIndex - nOldChars); + SetLength(nOldLength + nDelta); + } + else if (nDelta > 0) + { + char *pBuf = PrepareBuffer(nOldLength + nDelta, true); + if (!pBuf) + { + return false; + } + + memmove(pBuf + nIndex + nNewChars, pBuf + nIndex + nOldChars, nOldLength - nIndex - nOldChars); + } + + if (nNewChars) + { + memcpy(Access() + nIndex, pNewStr, nNewChars); + } + + return true; +} + + +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Purpose: Truncates the string to the specified number of characters +//----------------------------------------------------------------------------- +inline void CUtlStringBuilder::Truncate(size_t nChars) +{ + if (IsEmpty() || HasError()) + return; + + size_t nLen = Length(); + if (nLen <= nChars) + return; + + // we may be shortening enough to fit in the small buffer, but + // the external buffer is already allocated, so just keep using it. + m_data.SetLength(nChars); +} + + +//----------------------------------------------------------------------------- +// Data and memory validation +//----------------------------------------------------------------------------- +#ifdef DBGFLAG_VALIDATE +inline void CUtlStringBuilder::Validate(CValidator &validator, const char *pchName) +{ +#ifdef _WIN32 + validator.Push(typeid(*this).raw_name(), this, pchName); +#else + validator.Push(typeid(*this).name(), this, pchName); +#endif + + if (m_data.IsHeap()) + validator.ClaimMemory(Access()); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + + +#endif // UTLSTRING_H