1002 lines
23 KiB
C++
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
|