Files
scummvm-cursorfix/engines/bagel/boflib/string.cpp
2026-02-02 04:50:13 +01:00

600 lines
13 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/str.h"
#include "bagel/boflib/string.h"
#include "bagel/boflib/misc.h"
#include "bagel/boflib/string_functions.h"
namespace Bagel {
#define MAX_STRING 256
#define BUF_EXTRA 20
CBofString::CBofString() {
init();
}
CBofString::CBofString(int nLength) {
init();
allocBuffer(nLength);
}
CBofString::CBofString(const CBofString &cString) {
init();
copy(cString._pszData);
}
CBofString::CBofString(const char *pszBuf) {
init();
copy(pszBuf);
}
CBofString::CBofString(char *pszBuff, int pszBuffLen) {
init();
// Force a max length (To stop uninitialized buffer from killing us)
*(pszBuff + pszBuffLen - 1) = '\0';
// Passing in un-initialized data could cause problems depending on what
// string functions are used after this constructor.
_nLength = (uint16)strlen(pszBuff);
// Use the high byte of the buffer size to determine if we're using stack memory.
// Make sure that we don't have an obscenely large string
assert((pszBuffLen & mUseStackMem) == false);
SETBUFFERSIZE(pszBuffLen, true);
_pszData = pszBuff;
}
CBofString::CBofString(char ch, int nRepeat) {
assert(nRepeat > 0);
init();
allocBuffer(nRepeat);
memset(_pszData, ch, nRepeat);
}
CBofString::~CBofString() {
assert(isValidObject(this));
free();
}
void CBofString::init() {
assert(isValidObject(this));
SETBUFFERSIZE(0, false);
_nLength = 0;
_pszData = nullptr;
}
void CBofString::allocBuffer(int nLen) {
assert(isValidObject(this));
assert(nLen >= 0);
// These 3 lines "should" do the same thing
assert(nLen < SHRT_MAX); // max size (enough room for 1 extra)
assert((uint16)nLen < mUseStackMem);
assert((nLen & mUseStackMem) == 0);
// Delete any previous buffer
free();
// Don't do anything about zero length allocations
if (nLen > 0) {
// Allocate a buffer filled with 0s
_pszData = (char *)bofCleanAlloc(nLen + 1);
}
_nLength = 0;
// Use the high byte of the bufflen to indicate if we're using
// stack mem or not. Make sure our buff size is not too big
assert((nLen & mUseStackMem) == 0);
SETBUFFERSIZE(nLen, false);
}
void CBofString::free() {
assert(isValidObject(this));
bool bStackMem = USESSTACKMEM();
// Free any attached data
// Only free if it's not stack memory
if (_pszData != nullptr && bStackMem == false) {
bofFree(_pszData);
_pszData = nullptr;
}
_nLength = 0;
SETBUFFERSIZE(0, bStackMem);
}
void CBofString::copy(const char *pszBuf) {
assert(isValidObject(this));
if (NORMALIZEBUFFERSIZE() != 0)
*_pszData = '\0';
_nLength = 0;
if (pszBuf != nullptr) {
int n = strlen(pszBuf);
if (NORMALIZEBUFFERSIZE() <= n) {
allocBuffer(n + 1);
}
Common::strcpy_s(_pszData, n + 1, pszBuf);
_nLength = (uint16)strlen(_pszData);
}
}
void CBofString::allocCopy(CBofString &dest, int nCopyLen, int nCopyIndex, int nExtraLen) const {
assert(isValidObject(this));
// Will clone the data attached to this string allocating 'nExtraLen' characters
// Places results in uninitialized string 'dest'
// Will copy the part or all of original data to start of new string
int nNewLen = nCopyLen + nExtraLen;
if (nNewLen == 0) {
dest.free();
} else {
dest.allocBuffer(nNewLen);
assert(_pszData != nullptr);
memcpy(dest._pszData, &_pszData[nCopyIndex], nCopyLen * sizeof(char));
}
}
const CBofString &CBofString::operator=(const CBofString &cString) {
assert(isValidObject(this));
copy(cString._pszData);
return *this;
}
const CBofString &CBofString::operator=(const char *lpsz) {
assert(isValidObject(this));
copy(lpsz);
return *this;
}
void CBofString::concatCopy(int nSrc1Len, const char *lpszSrc1Data, int nSrc2Len, const char *lpszSrc2Data, int nAllocLen) {
assert(isValidObject(this));
// -- Master concatenation routine
// concatenate two sources
// -- Assume that 'this' is a new CBofString object
int nNewLen = nSrc1Len + nSrc2Len;
allocBuffer((nAllocLen == 0 ? nNewLen : nAllocLen));
memcpy(_pszData, lpszSrc1Data, nSrc1Len * sizeof(char));
memcpy(&_pszData[nSrc1Len], lpszSrc2Data, nSrc2Len * sizeof(char));
// RMS
_nLength = (uint16)nNewLen;
}
CBofString operator+(const CBofString &string1, const CBofString &string2) {
CBofString s;
s.concatCopy(string1._nLength, string1._pszData, string2._nLength, string2._pszData);
return s;
}
CBofString operator+(const CBofString &string, const char *lpsz) {
CBofString s;
s.concatCopy(string._nLength, string._pszData, CBofString::safeStrlen(lpsz), lpsz);
return s;
}
CBofString operator+(const char *lpsz, const CBofString &string) {
CBofString s;
s.concatCopy(CBofString::safeStrlen(lpsz), lpsz, string._nLength, string._pszData);
return s;
}
void CBofString::concatInPlace(int nSrcLen, const char *lpszSrcData) {
char szLocalBuff[512];
assert(isValidObject(this));
// -- The main routine for += operators
// If the buffer is too small, or we have a width mis-match, just
// allocate a new buffer (slow but sure)
//
// Make sure we have an underlying buffer.
if (_nLength + nSrcLen >= NORMALIZEBUFFERSIZE()) {
// Don't increment by buf extra, but set the size if we're
// less than that default threshold.
int nAllocAmount = nSrcLen;
if (nAllocAmount < BUF_EXTRA) {
nAllocAmount = BUF_EXTRA;
}
if (NORMALIZEBUFFERSIZE() == 0) {
allocBuffer(_nLength + nAllocAmount);
memcpy(_pszData, lpszSrcData, nSrcLen);
*(_pszData + nSrcLen) = '\0';
_nLength = (uint16)(_nLength + (uint16)nSrcLen);
} else {
assert(_pszData != nullptr);
// We have to grow the buffer, use the concat in place routine
char *lpszOldData;
if ((_nLength + nSrcLen + 1) < 512)
lpszOldData = szLocalBuff;
else
lpszOldData = new char[_nLength + nSrcLen + 1];
memcpy(lpszOldData, _pszData, (_nLength + 1) * sizeof(char));
concatCopy(_nLength, lpszOldData, nSrcLen, lpszSrcData, _nLength + nAllocAmount);
if (lpszOldData != szLocalBuff)
delete[] lpszOldData;
}
} else {
// Fast concatenation when buffer big enough
// Optimize for a single byte
if (nSrcLen == 1) {
_pszData[_nLength] = *lpszSrcData;
} else {
memcpy(&_pszData[_nLength], lpszSrcData, nSrcLen * sizeof(char));
}
_nLength = (uint16)(_nLength + (uint16)nSrcLen);
}
assert(_nLength <= NORMALIZEBUFFERSIZE());
_pszData[_nLength] = '\0';
}
const CBofString &CBofString::operator+=(const char *lpsz) {
assert(isValidObject(this));
concatInPlace(safeStrlen(lpsz), lpsz);
return *this;
}
const CBofString &CBofString::operator+=(char ch) {
assert(isValidObject(this));
concatInPlace(1, &ch);
return *this;
}
const CBofString &CBofString::operator+=(const CBofString &string) {
assert(isValidObject(this));
concatInPlace(string._nLength, string._pszData);
return *this;
}
char *CBofString::getBuffer() {
assert(isValidObject(this));
if (_pszData == nullptr) {
allocBuffer(1);
}
return _pszData;
}
int CBofString::findNumOccurrences(const char *pszSub) {
assert(isValidObject(this));
assert(pszSub != nullptr);
assert(*pszSub != '\0');
int nHits = 0;
if (_pszData != nullptr) {
char *pszCur = _pszData;
while (pszCur != nullptr) {
pszCur = strstr(pszCur, pszSub);
if (pszCur != nullptr) {
nHits++;
pszCur++;
}
}
}
return nHits;
}
const CBofString &CBofString::operator=(char ch) {
assert(isValidObject(this));
char szBuf[4];
Common::sprintf_s(szBuf, "%c", ch);
copy(szBuf);
return *this;
}
CBofString operator+(const CBofString &string1, char ch) {
CBofString s;
s.concatCopy(string1._nLength, string1._pszData, 1, &ch);
return s;
}
CBofString operator+(char ch, const CBofString &string) {
CBofString s;
s.concatCopy(1, &ch, string._nLength, string._pszData);
return s;
}
CBofString CBofString::mid(int nFirst) const {
assert(isValidObject(this));
return mid(nFirst, _nLength - nFirst);
}
CBofString CBofString::mid(int nFirst, int nCount) const {
assert(isValidObject(this));
assert(nFirst >= 0);
assert(nCount >= 0);
// Out-of-bounds requests return sensible things
if (nFirst + nCount > _nLength)
nCount = _nLength - nFirst;
if (nFirst > _nLength)
nCount = 0;
CBofString dest;
allocCopy(dest, nCount, nFirst, 0);
return dest;
}
CBofString CBofString::right(int nCount) const {
assert(isValidObject(this));
assert(nCount >= 0);
if (nCount > _nLength)
nCount = _nLength;
CBofString dest;
allocCopy(dest, nCount, _nLength - nCount, 0);
return dest;
}
CBofString CBofString::left(int nCount) const {
assert(isValidObject(this));
assert(nCount >= 0);
if (nCount > _nLength)
nCount = _nLength;
CBofString dest;
allocCopy(dest, nCount, 0, 0);
return dest;
}
void CBofString::deleteLastChar() {
if (!isEmpty()) {
*(_pszData + _nLength - 1) = '\0';
--_nLength;
}
}
// Find a sub-string (like strstr)
int CBofString::find(const char *lpszSub) const {
assert(isValidObject(this));
// Find first matching substring
char *lpsz = nullptr;
if (_pszData != nullptr)
lpsz = strstr(_pszData, lpszSub);
// Return -1 for not found, distance from beginning otherwise
return (lpsz == nullptr) ? -1 : (int)(lpsz - _pszData);
}
// CBofString formatting
#define FORCE_ANSI 0x10000
#define FORCE_UNICODE 0x20000
int CBofString::safeStrlen(const char *psz) {
return (psz == nullptr) ? 0 : strlen(psz);
}
// CBofString support (windows specific)
//
int CBofString::compare(const char *psz) const {
assert(isValidObject(this));
assert(psz != nullptr);
int n = -1;
if (_pszData != nullptr)
n = strcmp(_pszData, psz);
return n;
}
int CBofString::compareNoCase(const char *psz) const {
assert(isValidObject(this));
int n = -1;
if (_pszData != nullptr)
n = scumm_stricmp(_pszData, psz);
return n;
}
char CBofString::getAt(int nIndex) {
assert(isValidObject(this));
assert(nIndex >= 0);
assert(nIndex < _nLength);
return _pszData[nIndex];
}
char CBofString::operator[](int nIndex) {
assert(isValidObject(this));
return getAt(nIndex);
}
void CBofString::replaceCharAt(int nIndex, char chNew) {
if (_pszData != nullptr && nIndex < _nLength) {
_pszData[nIndex] = chNew;
}
}
void CBofString::replaceChar(char chOld, char chNew) {
assert(isValidObject(this));
// Would never find the terminator
assert(chOld != '\0');
if (_pszData != nullptr) {
// Walk through the string and replace the specified character
char *p = _pszData;
for (int i = 0; i < _nLength; i++) {
if (*p == chOld) {
*p = chNew;
// If we just inserted the terminator, then the length of
// this string has been changed, and we don't have to search
// any more.
//
if (chNew == '\0') {
_nLength = (uint16)i;
break;
}
}
p++;
}
}
}
void CBofString::replaceStr(const char *pszOld, const char *pszNew) {
assert(isValidObject(this));
assert(pszOld != nullptr);
assert(pszNew != nullptr);
if (_pszData != nullptr) {
char *p, *pszSearch;
int nOldLen = strlen(pszOld);
int nNewLen = strlen(pszNew);
// 1st pass to determine how much more storage space we might need
if (nNewLen > nOldLen) {
int nDiff = nNewLen - nOldLen;
int nNeedLength = _nLength + 1;
p = _pszData;
pszSearch = strstr(p, pszOld);
while (pszSearch != nullptr) {
p = pszSearch + nOldLen;
nNeedLength += nDiff;
pszSearch = strstr(p, pszOld);
}
// If we need more storage space for the buffer, then get some
if (nNeedLength > NORMALIZEBUFFERSIZE()) {
growTo(nNeedLength);
}
}
// Loop on the search and replace
// Make sure we loop on this, not just once as we can have several occurrences
// of the token that we are searching for.
p = _pszData;
pszSearch = strstr(p, pszOld);
while (pszSearch != nullptr) {
strreplaceStr(p, pszOld, pszNew);
p = pszSearch + nNewLen;
pszSearch = strstr(p, pszOld);
}
// Get new length
_nLength = (uint16)strlen(_pszData);
}
}
void CBofString::growTo(int nNewSize) {
assert(isValidObject(this));
// If there is nothing in the buffer to save, then just allocate what
// is needed
if (_nLength == 0) {
allocBuffer(nNewSize);
} else {
// Otherwise, we must keep track of whats in the buffer
// Create a temp buffer to save string
char *p = (char *)bofAlloc(_nLength + 2);
// Save copy of string
Common::strcpy_s(p, MAX_STRING, _pszData);
// Make the new buffer
allocBuffer(nNewSize);
// Copy saved string back
strncpy(_pszData, p, nNewSize - 1);
// Get it's new length
_nLength = (uint16)strlen(_pszData);
// Don't need temp buffer anymore
bofFree(p);
}
}
int CBofString::hash() const {
int returnValue = 0;
// Needs to be case in-sensitive
for (int i = 0; i < _nLength; i++) {
returnValue = returnValue + (char)toupper(_pszData[i]);
}
return returnValue;
}
void CBofString::makeUpper() {
Common::String s(_pszData);
s.toUppercase();
strncpy(_pszData, s.c_str(), _nLength);
}
} // namespace Bagel