Files
scummvm-cursorfix/engines/ags/shared/util/string.cpp
2026-02-02 04:50:13 +01:00

1002 lines
23 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/util.h"
#include "ags/shared/util/math.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string.h"
#include "ags/shared/util/string_compat.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
String::String()
: _cstr(const_cast<char *>(""))
, _len(0)
, _buf(nullptr) {
}
String::String(const String &str)
: _cstr(const_cast<char *>(""))
, _len(0)
, _buf(nullptr) {
*this = str;
}
String::String(String &&str) {
_cstr = str._cstr;
_len = str._len;
_buf = str._buf;
_bufHead = str._bufHead;
str._cstr = const_cast<char *>("");
str._len = 0;
str._buf = nullptr;
str._bufHead = nullptr;
}
String::String(const char *cstr)
: _cstr(const_cast<char *>(""))
, _len(0)
, _buf(nullptr) {
*this = cstr;
}
String::String(const Common::String &s)
: _cstr(nullptr),
_len(0),
_buf(nullptr) {
*this = s.c_str();
}
String::String(const char *cstr, size_t length)
: _cstr(const_cast<char *>(""))
, _len(0)
, _buf(nullptr) {
SetString(cstr, length);
}
String::String(char c, size_t count)
: _cstr(const_cast<char *>(""))
, _len(0)
, _buf(nullptr) {
FillString(c, count);
}
String::~String() {
Free();
}
bool String::IsNullOrSpace() const {
if (_len == 0)
return true;
for (const char *ptr = _cstr; *ptr; ++ptr) {
if (!Common::isSpace(static_cast<uint8_t>(*ptr)))
return false;
}
return true;
}
void String::Read(Stream *in, size_t max_chars, bool stop_at_limit) {
Empty();
if (!in) {
return;
}
if (max_chars == 0 && stop_at_limit) {
return;
}
char buffer[1024];
char *read_ptr = buffer;
size_t read_size = 0;
int ichar;
do {
ichar = in->ReadByte();
read_size++;
if (read_size > max_chars) {
continue;
}
*read_ptr = (char)(ichar >= 0 ? ichar : 0);
if (!*read_ptr || ((read_ptr - buffer) == (sizeof(buffer) - 1 - 1))) {
buffer[sizeof(buffer) - 1] = 0;
Append(buffer);
read_ptr = buffer;
} else {
read_ptr++;
}
} while (ichar > 0 && !(stop_at_limit && read_size == max_chars));
}
void String::ReadCount(Stream *in, size_t count) {
if (in && count > 0) {
ReserveAndShift(false, count);
count = in->Read(_cstr, count);
_cstr[count] = 0;
_len = strlen(_cstr);
} else {
Empty();
}
}
void String::Write(Stream *out) const {
if (out) {
out->Write(_cstr, _len + 1);
}
}
void String::WriteCount(Stream *out, size_t count) const {
if (out) {
size_t str_out_len = MIN(count - 1, _len);
if (str_out_len > 0)
out->Write(_cstr, str_out_len);
size_t null_out_len = count - str_out_len;
if (null_out_len > 0)
out->WriteByteCount(0, null_out_len);
}
}
int String::Compare(const char *cstr) const {
return strcmp(_cstr, cstr ? cstr : "");
}
int String::CompareNoCase(const char *cstr) const {
return ags_stricmp(_cstr, cstr ? cstr : "");
}
int String::CompareLeft(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
return strncmp(_cstr, cstr, count != NoIndex ? count : strlen(cstr));
}
int String::CompareLeftNoCase(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
return ags_strnicmp(_cstr, cstr, count != NoIndex ? count : strlen(cstr));
}
int String::CompareMid(const char *cstr, size_t from, size_t count) const {
cstr = cstr ? cstr : "";
from = MIN(from, _len);
return strncmp(_cstr + from, cstr, count != NoIndex ? count : strlen(cstr));
}
int String::CompareMidNoCase(const char *cstr, size_t from, size_t count) const {
cstr = cstr ? cstr : "";
from = MIN(from, _len);
return ags_strnicmp(_cstr + from, cstr, count != NoIndex ? count : strlen(cstr));
}
int String::CompareRight(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
count = count != NoIndex ? count : strlen(cstr);
size_t off = MIN(_len, count);
return strncmp(_cstr + _len - off, cstr, count);
}
int String::CompareRightNoCase(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
count = count != NoIndex ? count : strlen(cstr);
size_t off = MIN(_len, count);
return ags_strnicmp(_cstr + _len - off, cstr, count);
}
size_t String::FindChar(char c, size_t from) const {
if (c && from < _len) {
const char *found_cstr = strchr(_cstr + from, c);
return found_cstr ? found_cstr - _cstr : NoIndex;
}
return NoIndex;
}
size_t String::FindCharReverse(char c, size_t from) const {
if ((_len == 0) || !c) {
return NoIndex;
}
from = MIN(from, _len - 1);
const char *seek_ptr = _cstr + from;
while (seek_ptr >= _cstr) {
if (*seek_ptr == c) {
return seek_ptr - _cstr;
}
seek_ptr--;
}
return NoIndex;
}
size_t String::FindString(const char *cstr, size_t from) const {
if (cstr && from < _len) {
const char *found_cstr = strstr(_cstr + from, cstr);
return found_cstr ? found_cstr - _cstr : NoIndex;
}
return NoIndex;
}
bool String::FindSection(char separator, size_t first, size_t last, bool exclude_first_sep, bool exclude_last_sep,
size_t &from, size_t &to) const {
if ((_len == 0) || !separator) {
return false;
}
if (first > last) {
return false;
}
size_t this_field = 0;
size_t slice_from = 0;
size_t slice_to = _len;
size_t slice_at = NoIndex;
do {
slice_at = FindChar(separator, slice_at + 1);
if (slice_at == NoIndex)
slice_at = _len;
// found where previous field ends
if (this_field == last) {
// if previous field is the last one to be included,
// then set the section tail
slice_to = exclude_last_sep ? slice_at : slice_at + 1;
}
if (slice_at != _len) {
this_field++;
if (this_field == first) {
// if the new field is the first one to be included,
// then set the section head
slice_from = exclude_first_sep ? slice_at + 1 : slice_at;
}
}
} while (slice_at < _len && this_field <= last);
// the search is a success if at least the first field was found
if (this_field >= first) {
// correct the indices to stay in the [0; length] range
assert(slice_from <= slice_to);
from = Math::Clamp(slice_from, (size_t)0, _len);
to = Math::Clamp(slice_to, (size_t)0, _len);
return true;
}
return false;
}
int String::ToInt() const {
return atoi(_cstr);
}
String String::Wrapper(const char *cstr) {
String str;
// Note that String will NOT *modify* the const buffer.
// Any write operation on the buffer is preceded by a call to BecomeUnique.
str._cstr = const_cast<char *>(cstr ? cstr : "");
str._len = strlen(str._cstr);
return str;
}
/* static */ String String::FromFormat(const char *fcstr, ...) {
fcstr = fcstr ? fcstr : "";
String str;
va_list argptr;
va_start(argptr, fcstr);
str.FormatV(fcstr, argptr);
va_end(argptr);
return str;
}
/* static */ String String::FromFormatV(const char *fcstr, va_list argptr) {
String str;
str.FormatV(fcstr, argptr);
return str;
}
/* static */ String String::FromStream(Stream *in, size_t max_chars, bool stop_at_limit) {
String str;
str.Read(in, max_chars, stop_at_limit);
return str;
}
/* static */ String String::FromStreamCount(Stream *in, size_t count) {
String str;
str.ReadCount(in, count);
return str;
}
String String::Lower() const {
String str = *this;
str.MakeLower();
return str;
}
String String::Upper() const {
String str = *this;
str.MakeUpper();
return str;
}
String String::Left(size_t count) const {
count = MIN(count, _len);
return count == _len ? *this : String(_cstr, count);
}
String String::Mid(size_t from, size_t count) const {
Math::ClampLength(from, count, (size_t)0, _len);
return count == _len ? *this : String(_cstr + from, count);
}
String String::Right(size_t count) const {
count = MIN(count, _len);
return count == _len ? *this : String(_cstr + _len - count, count);
}
String String::LeftSection(char separator, bool exclude_separator) const {
if ((_len != 0) && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != NoIndex) {
slice_at = exclude_separator ? slice_at : slice_at + 1;
return Left(slice_at);
}
}
return *this;
}
String String::RightSection(char separator, bool exclude_separator) const {
if ((_len != 0) && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != NoIndex) {
size_t count = exclude_separator ? _len - slice_at - 1 : _len - slice_at;
return Right(count);
}
}
return *this;
}
String String::Section(char separator, size_t first, size_t last,
bool exclude_first_sep, bool exclude_last_sep) const {
if ((_len == 0) || !separator) {
return String();
}
size_t slice_from;
size_t slice_to;
if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep,
slice_from, slice_to)) {
return Mid(slice_from, slice_to - slice_from);
}
return String();
}
std::vector<String> String::Split(char separator) const {
std::vector<String> result;
if (!separator)
return result;
const char *ptr = _cstr;
while (ptr && *ptr) {
const char *found_cstr = strchr(ptr, separator);
if (!found_cstr) break;
result.push_back(String(ptr, found_cstr - ptr));
ptr = found_cstr + 1;
}
result.push_back(String(ptr));
return result;
}
void String::Reserve(size_t max_length) {
if (_bufHead) {
if (max_length > _bufHead->Capacity) {
// grow by 50%
size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity / 2);
Copy(MAX(max_length, grow_length));
}
} else {
Create(max_length);
}
}
void String::ReserveMore(size_t more_length) {
Reserve(_len + more_length);
}
void String::Compact() {
if (_bufHead && _bufHead->Capacity > _len) {
Copy(_len);
}
}
void String::Append(const String &str) {
if (str._len > 0) {
ReserveAndShift(false, str._len);
memcpy(_cstr + _len, str._cstr, str._len);
_len += str._len;
_cstr[_len] = 0;
}
}
void String::Append(const char *cstr, size_t len) {
if (len == 0)
return;
// Test for null-terminator in the range
const char *ptr = cstr;
for (; *ptr && (static_cast<size_t>(ptr - cstr) < len); ++ptr);
if (static_cast<size_t>(ptr - cstr) < len)
len = ptr - cstr;
ReserveAndShift(false, len);
memcpy(_cstr + _len, cstr, len);
_len += len;
_cstr[_len] = 0;
}
void String::AppendChar(char c) {
if (c) {
ReserveAndShift(false, 1);
_cstr[_len++] = c;
_cstr[_len] = 0;
}
}
void String::AppendFmt(const char *fcstr, ...) {
va_list argptr;
va_start(argptr, fcstr);
AppendFmtv(fcstr, argptr);
va_end(argptr);
}
void String::AppendFmtv(const char *fcstr, va_list argptr) {
fcstr = fcstr ? fcstr : "";
va_list argptr_cpy;
va_copy(argptr_cpy, argptr);
size_t length = vsnprintf(nullptr, 0u, fcstr, argptr);
ReserveAndShift(false, length);
vsnprintf(_cstr + _len, length + 1, fcstr, argptr_cpy);
va_end(argptr_cpy);
_len += length;
_cstr[_len] = 0;
}
void String::ClipLeft(size_t count) {
if ((_len != 0) && (count > 0)) {
count = MIN(count, _len);
BecomeUnique();
_len -= count;
_cstr += count;
}
}
void String::ClipMid(size_t from, size_t count) {
if (from < _len) {
count = MIN(count, _len - from);
if (count > 0) {
BecomeUnique();
if (!from) {
_len -= count;
_cstr += count;
} else if (from + count == _len) {
_len -= count;
_cstr[_len] = 0;
} else {
char *cstr_mid = _cstr + from;
memmove(cstr_mid, _cstr + from + count, _len - from - count + 1);
_len -= count;
}
}
}
}
void String::ClipRight(size_t count) {
if (_len > 0 && count > 0) {
count = MIN(count, _len);
BecomeUnique();
_len -= count;
_cstr[_len] = 0;
}
}
void String::ClipLeftSection(char separator, bool include_separator) {
if ((_len != 0) && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != NoIndex) {
ClipLeft(include_separator ? slice_at + 1 : slice_at);
} else
Empty();
}
}
void String::ClipRightSection(char separator, bool include_separator) {
if ((_len != 0) && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != NoIndex) {
ClipRight(include_separator ? _len - slice_at : _len - slice_at - 1);
} else
Empty();
}
}
void String::ClipSection(char separator, size_t first, size_t last,
bool include_first_sep, bool include_last_sep) {
if ((_len == 0) || !separator) {
return;
}
size_t slice_from;
size_t slice_to;
if (FindSection(separator, first, last, !include_first_sep, !include_last_sep,
slice_from, slice_to)) {
ClipMid(slice_from, slice_to - slice_from);
}
}
void String::Empty() {
if (IsShared()) {
Free();
} else {
_len = 0;
_cstr[0] = 0;
}
}
void String::FillString(char c, size_t count) {
if (count > 0) {
ReserveAndShift(false, count);
memset(_cstr, c, count);
_len = count;
_cstr[count] = 0;
} else {
Empty();
}
}
void String::Format(const char *fcstr, ...) {
va_list argptr;
va_start(argptr, fcstr);
FormatV(fcstr, argptr);
va_end(argptr);
}
void String::FormatV(const char *fcstr, va_list argptr) {
fcstr = fcstr ? fcstr : "";
va_list argptr_cpy;
va_copy(argptr_cpy, argptr);
size_t length = vsnprintf(nullptr, 0u, fcstr, argptr);
ReserveAndShift(false, Math::Surplus(length, _len));
vsnprintf(_cstr, length + 1, fcstr, argptr_cpy);
va_end(argptr_cpy);
_len = length;
_cstr[_len] = 0;
}
void String::Free() {
if (_bufHead) {
assert(_bufHead->RefCount > 0);
_bufHead->RefCount--;
if (!_bufHead->RefCount) {
delete[] _buf;
}
}
_buf = nullptr;
_cstr = const_cast<char *>("");
_len = 0;
}
void String::MakeLower() {
if (_len != 0) {
BecomeUnique();
ags_strlwr(_cstr);
}
}
void String::MakeUpper() {
if (_len != 0) {
BecomeUnique();
ags_strupr(_cstr);
}
}
void String::MergeSequences(char c) {
if (_len <= 1)
return; // no point merging if 1 char or less
BecomeUnique();
char last = 0;
char *wp = _cstr;
for (char *rp = _cstr; *rp; ++rp) {
if ((c && *rp != c) || *rp != last)
*(wp++) = *rp;
last = *rp;
}
*wp = 0;
_len = wp - _cstr;
}
void String::Prepend(const String &str) {
size_t length = str._len;
if (length > 0) {
ReserveAndShift(true, length);
memcpy(_cstr - length, str._cstr, length);
_len += length;
_cstr -= length;
}
}
void String::PrependChar(char c) {
if (c) {
ReserveAndShift(true, 1);
_len++;
_cstr--;
_cstr[0] = c;
}
}
void String::Replace(char what, char with) {
if ((_len == 0) || !what || !with || (what == with))
return;
char *ptr = _cstr;
// Special case for calling BecomeUnique on the first find
if (IsShared()) {
for (; *ptr; ++ptr) {
if (*ptr == what) {
ptrdiff_t diff = ptr - _cstr;
BecomeUnique();
ptr = _cstr + diff; // BecomeUnique will realloc memory
break;
}
}
}
// Do the full search & replace
for (; *ptr; ++ptr) {
if (*ptr == what)
*ptr = with;
}
}
void String::Replace(const String &what, const String &with) {
if ((what._len == 0) || (_len < what._len) || strcmp(what._cstr, with._cstr) == 0)
return;
const size_t len_src = what._len;
const size_t len_dst = with._len;
const size_t len_add = Math::Surplus(len_dst, len_src);
char *ptr = strstr(_cstr, what._cstr);
if (!ptr)
return; // pattern not found once, bail out early
// Special case for calling BecomeUnique on the first find
if (IsShared() && len_add == 0) {
ptrdiff_t diff = ptr - _cstr;
BecomeUnique(); // if same length make a unique copy once
ptr = _cstr + diff; // BecomeUnique will realloc memory
}
// Do the full search & replace
for (; ptr; ptr = strstr(ptr, what._cstr)) {
if (len_add > 0) {
ptrdiff_t diff = ptr - _cstr;
ReserveAndShift(false, len_add);
ptr = _cstr + diff; // ReserveAndShift may realloc memory
}
if (len_src != len_dst)
memmove(ptr + len_dst, ptr + len_src, _len - (ptr - _cstr + len_src) + 1);
memcpy(ptr, with._cstr, len_dst);
_len += len_dst - len_src;
ptr += len_dst;
}
}
void String::ReplaceMid(size_t from, size_t count, const String &str) {
size_t length = str._len;
Math::ClampLength(from, count, (size_t)0, _len);
ReserveAndShift(false, Math::Surplus(length, count));
if (count != str._len)
memmove(_cstr + from + length, _cstr + from + count, _len - (from + count) + 1);
memcpy(_cstr + from, str._cstr, length);
_len += length - count;
}
void String::Reverse() {
if (_len <= 1)
return; // nothing to reverse if 1 char or less
BecomeUnique();
for (char *fw = _cstr, *bw = _cstr + _len - 1;
fw < bw; ++fw, --bw) {
SWAP(*fw, *bw);
}
}
void String::ReverseUTF8() {
if (_len <= 1)
return; // nothing to reverse if 1 char or less
// TODO: may this be optimized to not alloc new buffer?
// otherwise, allocate a proper String data buf and replace existing
char *newstr = new char[_len + 1];
for (char *fw = _cstr, *fw2 = _cstr + 1,
*bw = _cstr + _len - 1, *bw2 = _cstr + _len;
fw <= bw; // FIXME: <= catches odd middle char, optimize?
fw = fw2++, bw2 = bw--) {
// find end of next character forwards
for (; (fw2 < bw) && ((*fw2 & 0xC0) == 0x80); ++fw2);
// find beginning of the prev character backwards
for (; (bw > fw) && ((*bw & 0xC0) == 0x80); --bw);
// put these in opposite sides on the new buffer
char *fw_place = newstr + (_cstr + _len - bw2);
char *bw_place = newstr + _len - (fw2 - _cstr);
memcpy(fw_place, bw, bw2 - bw);
if (fw != bw) // FIXME, optimize?
memcpy(bw_place, fw, fw2 - fw);
}
newstr[_len] = 0;
SetString(newstr);
delete[] newstr;
}
void String::SetAt(size_t index, char c) {
if ((index < _len) && c) {
BecomeUnique();
_cstr[index] = c;
}
}
void String::SetString(const char *cstr, size_t length) {
if (cstr) {
length = MIN(length, strlen(cstr));
if (length > 0) {
ReserveAndShift(false, Math::Surplus(length, _len));
memcpy(_cstr, cstr, length);
_len = length;
_cstr[length] = 0;
} else {
Empty();
}
} else {
Empty();
}
}
void String::Trim(char c) {
TrimLeft(c);
TrimRight(c);
}
void String::TrimLeft(char c) {
if (_len == 0) {
return;
}
const char *trim_ptr = _cstr;
for (;;) {
auto t = *trim_ptr;
if (t == 0) {
break;
}
if (c && t != c) {
break;
}
if (!c && !Common::isSpace(static_cast<uint8_t>(t))) {
break;
}
trim_ptr++;
}
size_t trimmed = trim_ptr - _cstr;
if (trimmed > 0) {
BecomeUnique();
_len -= trimmed;
_cstr += trimmed;
}
}
void String::TrimRight(char c) {
if (_len == 0) {
return;
}
const char *trim_ptr = _cstr + _len - 1;
for (;;) {
if (trim_ptr < _cstr) {
break;
}
auto t = *trim_ptr;
if (c && t != c) {
break;
}
if (!c && !Common::isSpace(static_cast<uint8_t>(t))) {
break;
}
trim_ptr--;
}
size_t trimmed = (_cstr + _len - 1) - trim_ptr;
if (trimmed > 0) {
BecomeUnique();
_len -= trimmed;
_cstr[_len] = 0;
}
}
void String::TruncateToLeft(size_t count) {
if (_len != 0) {
count = MIN(count, _len);
if (count < _len) {
BecomeUnique();
_len = count;
_cstr[_len] = 0;
}
}
}
void String::TruncateToMid(size_t from, size_t count) {
if (_len != 0) {
Math::ClampLength(from, count, (size_t)0, _len);
if (from > 0 || count < _len) {
BecomeUnique();
_len = count;
_cstr += from;
_cstr[_len] = 0;
}
}
}
void String::TruncateToRight(size_t count) {
if (_len != 0) {
count = MIN(count, _len);
if (count < _len) {
BecomeUnique();
_cstr += _len - count;
_len = count;
}
}
}
void String::TruncateToLeftSection(char separator, bool exclude_separator) {
if ((_len != 0) && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != NoIndex) {
TruncateToLeft(exclude_separator ? slice_at : slice_at + 1);
}
}
}
void String::TruncateToRightSection(char separator, bool exclude_separator) {
if ((_len != 0) && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != NoIndex) {
TruncateToRight(exclude_separator ? _len - slice_at - 1 : _len - slice_at);
}
}
}
void String::TruncateToSection(char separator, size_t first, size_t last,
bool exclude_first_sep, bool exclude_last_sep) {
if ((_len == 0) || !separator) {
return;
}
size_t slice_from;
size_t slice_to;
if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep,
slice_from, slice_to)) {
TruncateToMid(slice_from, slice_to - slice_from);
} else {
Empty();
}
}
void String::Wrap(const char *cstr) {
Free();
_buf = nullptr;
// Note that String will NOT *modify* the const buffer.
// Any write operation on the buffer is preceded by a call to BecomeUnique.
_cstr = const_cast<char *>(cstr ? cstr : "");
_len = strlen(_cstr);
}
String &String::operator=(const String &str) {
if (_cstr != str._cstr) {
Free();
_buf = str._buf;
_cstr = str._cstr;
_len = str._len;
if (_bufHead) {
_bufHead->RefCount++;
}
}
return *this;
}
String &String::operator=(String &&str) {
Free();
_cstr = str._cstr;
_len = str._len;
_buf = str._buf;
_bufHead = str._bufHead;
str._cstr = const_cast<char *>("");
str._len = 0;
str._buf = nullptr;
str._bufHead = nullptr;
return *this;
}
String &String::operator=(const char *cstr) {
SetString(cstr);
return *this;
}
void String::Create(size_t max_length) {
_buf = new char[sizeof(String::BufHeader) + max_length + 1];
_bufHead->RefCount = 1;
_bufHead->Capacity = max_length;
_len = 0;
_cstr = _buf + sizeof(String::BufHeader);
_cstr[_len] = 0;
}
void String::Copy(size_t max_length, size_t offset) {
char *new_data = new char[sizeof(String::BufHeader) + max_length + 1];
// remember, that _cstr may point to any address in buffer
char *cstr_head = new_data + sizeof(String::BufHeader) + offset;
size_t copy_length = MIN(_len, max_length);
memcpy(cstr_head, _cstr, copy_length);
Free();
_buf = new_data;
_bufHead->RefCount = 1;
_bufHead->Capacity = max_length;
_len = copy_length;
_cstr = cstr_head;
_cstr[_len] = 0;
}
void String::Align(size_t offset) {
char *cstr_head = _buf + sizeof(String::BufHeader) + offset;
memmove(cstr_head, _cstr, _len + 1);
_cstr = cstr_head;
}
inline bool String::IsShared() const {
// no allocated buffer == wrapping an external char[]
// has buffer and refcount > 1 == shared string buffer
return !_bufHead || (_bufHead->RefCount > 1);
}
void String::BecomeUnique() {
if (IsShared()) {
Copy(_len);
}
}
void String::ReserveAndShift(bool left, size_t more_length) {
if (_bufHead) {
size_t total_length = _len + more_length;
if (_bufHead->Capacity < total_length) { // not enough capacity - reallocate buffer
// grow by 50% or at least to total_size
size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity >> 1);
Copy(MAX(total_length, grow_length), left ? more_length : 0u);
} else if (_bufHead->RefCount > 1) { // is a shared string - clone buffer
Copy(total_length, left ? more_length : 0u);
} else {
// make sure we make use of all of our space
const char *cstr_head = _buf + sizeof(String::BufHeader);
size_t free_space = left ?
_cstr - cstr_head :
(cstr_head + _bufHead->Capacity) - (_cstr + _len);
if (free_space < more_length) {
Align((left ?
_cstr + (more_length - free_space) :
_cstr - (more_length - free_space)) - cstr_head);
}
}
} else { // either empty string, or wrapping external char[] - create new buffer
Create(more_length);
}
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3