r5sdk/r5dev/tier1/utlstring.h
Kawe Mazidjatari e886208d64 Remove extraneous destructor
This caused the 'CUtlBinaryBlock' destructor to be called twice (freeing memory on the same pointer twice). Destructor should only be called explicitly in 'CUtlBinaryBlock' itself to purge vector memory due to late binding of game's memalloc singleton.
2023-03-18 15:40:04 +01:00

1497 lines
46 KiB
C++

//====== 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( int64 growSize = 0, int64 initSize = 0 );
~CUtlBinaryBlock()
{
#ifdef _DEBUG
m_nActualLength = 0x7BADF00D;
#else
m_nActualLength = 0;
#endif
// Has to be explicitly called due to the
// current design of our SDK. Unlike other
// Source Engine games, we couldn't import
// the memalloc singleton as the executable
// is monolithic; we obtain it post init
// (too late for binding it against the
// new/delete operators..).
m_Memory.~CUtlMemory();
}
// NOTE: nInitialLength indicates how much of the buffer starts full
CUtlBinaryBlock( void* pMemory, int64 nSizeInBytes, int64 nInitialLength );
CUtlBinaryBlock( const void* pMemory, int64 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, int64 nMaxLen ) const;
void Set( const void *pValue, int64 nLen );
const void *Get( ) const;
void *Get( );
unsigned char& operator[]( int64 i );
const unsigned char& operator[]( int64 i ) const;
int64 Length() const;
void SetLength( int64 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<unsigned char> m_Memory;
int64 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 )
{
int64 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 int64 CUtlBinaryBlock::Length() const
{
return m_nActualLength;
}
inline unsigned char& CUtlBinaryBlock::operator[]( int64 i )
{
return m_Memory[i];
}
inline const unsigned char& CUtlBinaryBlock::operator[]( int64 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, int64 nSizeInBytes, int64 nInitialLength );
CUtlString( const void* pMemory, int64 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
int64 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( int64 nLen );
void Purge();
void Swap(CUtlString &src);
// Case Change
void ToUpper();
void ToLower( );
void Append( const char *pchAddition );
void Append(const char *pchAddition, int64 nMaxChars);
void Append(char chAddition) {
char temp[2] = { chAddition, 0 };
Append(temp);
}
// Strips the trailing slash
void StripTrailingSlash();
char operator[] ( int64 idx ) const;
char& operator[] ( int64 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+=( int64 rhs );
CUtlString &operator+=( double rhs );
CUtlString operator+( const char *pOther )const;
//CUtlString operator+( int64 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, int64 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( int64 nStart=0, int64 nEnd=INT_MAX );
// Grab a substring starting from the left or the right side.
CUtlString Left( int64 nChars );
CUtlString Right( int64 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 int64 __cdecl SortCaseInsensitive( const CUtlString *pString1, const CUtlString *pString2 );
static int64 __cdecl SortCaseSensitive( const CUtlString *pString1, const CUtlString *pString2 );
// From Src2
void FixSlashes( char cSeparator = CORRECT_PATH_SEPARATOR )
{
for ( int64 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 int64 __cdecl CUtlString::SortCaseInsensitive( const CUtlString *pString1, const CUtlString *pString2 )
{
return V_stricmp( pString1->String(), pString2->String() );
}
inline int64 __cdecl CUtlString::SortCaseSensitive( const CUtlString *pString1, const CUtlString *pString2 )
{
return V_strcmp( pString1->String(), pString2->String() );
}
inline char CUtlString::operator [] ( int64 index ) const
{
return Get()[index];
}
inline char& CUtlString::operator[] ( int64 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<char>
{
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<wchar_t>
{
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<T>::EmptyString(); }
operator const T*() const { return m_pString ? m_pString : StringFuncs<T>::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<T>::Set( const T *pValue )
{
if ( pValue != m_pString )
{
free( ( void* ) m_pString );
m_pString = pValue && pValue[0] ? StringFuncs<T>::Duplicate( pValue ) : NULL;
}
}
template < typename T >
int CUtlConstStringBase<T>::Compare( const T *rhs ) const
{
if ( m_pString )
{
if ( rhs )
return StringFuncs<T>::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<char> CUtlConstString;
typedef CUtlConstStringBase<wchar_t> 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, int64 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 <typename T>
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)
MemAllocSingleton()->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)
MemAllocSingleton()->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, int64 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