Files
scummvm/engines/ags/shared/util/string.cpp
T

851 lines
19 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "util/math.h"
#include "util/stream.h"
#include "util/string.h"
#include "util/string_compat.h"
namespace AGS {
namespace Common {
String::String()
: _cstr(nullptr)
, _len(0)
, _buf(nullptr) {
}
String::String(const String &str)
: _cstr(nullptr)
, _len(0)
, _buf(nullptr) {
*this = str;
}
String::String(const char *cstr)
: _cstr(nullptr)
, _len(0)
, _buf(nullptr) {
*this = cstr;
}
String::String(const char *cstr, size_t length)
: _cstr(nullptr)
, _len(0)
, _buf(nullptr) {
SetString(cstr, length);
}
String::String(char c, size_t count)
: _cstr(nullptr)
, _len(0)
, _buf(nullptr) {
FillString(c, count);
}
String::~String() {
Free();
}
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) {
Empty();
if (in && count > 0) {
ReserveAndShift(false, count);
count = in->Read(_cstr, count);
_cstr[count] = 0;
_len = strlen(_cstr);
}
}
void String::Write(Stream *out) const {
if (out) {
out->Write(GetCStr(), GetLength() + 1);
}
}
void String::WriteCount(Stream *out, size_t count) const {
if (out) {
size_t str_out_len = Math::Min(count - 1, GetLength());
if (str_out_len > 0)
out->Write(GetCStr(), str_out_len);
size_t null_out_len = count - str_out_len;
if (null_out_len > 0)
out->WriteByteCount(0, null_out_len);
}
}
/* static */ void String::WriteString(const char *cstr, Stream *out) {
if (out) {
cstr = cstr ? cstr : "";
out->Write(cstr, strlen(cstr) + 1);
}
}
int String::Compare(const char *cstr) const {
return strcmp(GetCStr(), cstr ? cstr : "");
}
int String::CompareNoCase(const char *cstr) const {
return ags_stricmp(GetCStr(), cstr ? cstr : "");
}
int String::CompareLeft(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
return strncmp(GetCStr(), cstr, count != -1 ? count : strlen(cstr));
}
int String::CompareLeftNoCase(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
return ags_strnicmp(GetCStr(), cstr, count != -1 ? count : strlen(cstr));
}
int String::CompareMid(const char *cstr, size_t from, size_t count) const {
cstr = cstr ? cstr : "";
from = Math::Min(from, GetLength());
return strncmp(GetCStr() + from, cstr, count != -1 ? count : strlen(cstr));
}
int String::CompareMidNoCase(const char *cstr, size_t from, size_t count) const {
cstr = cstr ? cstr : "";
from = Math::Min(from, GetLength());
return ags_strnicmp(GetCStr() + from, cstr, count != -1 ? count : strlen(cstr));
}
int String::CompareRight(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
count = count != -1 ? count : strlen(cstr);
size_t off = Math::Min(GetLength(), count);
return strncmp(GetCStr() + GetLength() - off, cstr, count);
}
int String::CompareRightNoCase(const char *cstr, size_t count) const {
cstr = cstr ? cstr : "";
count = count != -1 ? count : strlen(cstr);
size_t off = Math::Min(GetLength(), count);
return ags_strnicmp(GetCStr() + GetLength() - 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 : -1;
}
return -1;
}
size_t String::FindCharReverse(char c, size_t from) const {
if (!_cstr || !c) {
return -1;
}
from = Math::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 -1;
}
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 : -1;
}
return -1;
}
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 (!_cstr || !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 = -1;
do {
slice_at = FindChar(separator, slice_at + 1);
if (slice_at == -1)
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(GetCStr());
}
String String::Wrapper(const char *cstr) {
String str;
str.Wrap(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 = Math::Min(count, GetLength());
return count == GetLength() ? *this : String(GetCStr(), count);
}
String String::Mid(size_t from, size_t count) const {
Math::ClampLength(from, count, (size_t)0, GetLength());
return count == GetLength() ? *this : String(GetCStr() + from, count);
}
String String::Right(size_t count) const {
count = Math::Min(count, GetLength());
return count == GetLength() ? *this : String(GetCStr() + GetLength() - count, count);
}
String String::LeftSection(char separator, bool exclude_separator) const {
if (_cstr && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != -1) {
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 (_cstr && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != -1) {
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 (!_cstr || !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 (!_cstr || !separator)
return result;
const char *ptr = _cstr;
while (*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(Math::Max(max_length, grow_length));
}
} else {
Create(max_length);
}
}
void String::ReserveMore(size_t more_length) {
Reserve(GetLength() + more_length);
}
void String::Compact() {
if (_bufHead && _bufHead->Capacity > _len) {
Copy(_len);
}
}
void String::Append(const char *cstr) {
if (cstr) {
size_t length = strlen(cstr);
if (length > 0) {
ReserveAndShift(false, length);
memcpy(_cstr + _len, cstr, length);
_len += length;
_cstr[_len] = 0;
}
}
}
void String::AppendChar(char c) {
if (c) {
ReserveAndShift(false, 1);
_cstr[_len++] = c;
_cstr[_len] = 0;
}
}
void String::ClipLeft(size_t count) {
if (_len > 0 && count > 0) {
count = Math::Min(count, _len);
BecomeUnique();
_len -= count;
_cstr += count;
}
}
void String::ClipMid(size_t from, size_t count) {
if (from < _len) {
count = Math::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 (count > 0) {
count = Math::Min(count, GetLength());
BecomeUnique();
_len -= count;
_cstr[_len] = 0;
}
}
void String::ClipLeftSection(char separator, bool include_separator) {
if (_cstr && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != -1) {
ClipLeft(include_separator ? slice_at + 1 : slice_at);
} else
Empty();
}
}
void String::ClipRightSection(char separator, bool include_separator) {
if (_cstr && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != -1) {
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 (!_cstr || !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 (_cstr) {
BecomeUnique();
_len = 0;
_cstr[0] = 0;
}
}
void String::FillString(char c, size_t count) {
Empty();
if (count > 0) {
ReserveAndShift(false, count);
memset(_cstr, c, count);
_len = count;
_cstr[count] = 0;
}
}
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, GetLength()));
vsprintf(_cstr, 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 = nullptr;
_len = 0;
}
void String::MakeLower() {
if (_cstr) {
BecomeUnique();
ags_strlwr(_cstr);
}
}
void String::MakeUpper() {
if (_cstr) {
BecomeUnique();
ags_strupr(_cstr);
}
}
void String::Prepend(const char *cstr) {
if (cstr) {
size_t length = strlen(cstr);
if (length > 0) {
ReserveAndShift(true, length);
memcpy(_cstr - length, 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 (_cstr && what && with && what != with) {
BecomeUnique();
char *rep_ptr = _cstr;
while (*rep_ptr) {
if (*rep_ptr == what) {
*rep_ptr = with;
}
rep_ptr++;
}
}
}
void String::ReplaceMid(size_t from, size_t count, const char *cstr) {
if (!cstr)
cstr = "";
size_t length = strlen(cstr);
Math::ClampLength(from, count, (size_t)0, GetLength());
ReserveAndShift(false, Math::Surplus(length, count));
memmove(_cstr + from + length, _cstr + from + count, GetLength() - (from + count) + 1);
memcpy(_cstr + from, cstr, length);
_len += length - count;
}
void String::Reverse() {
if (!_cstr || GetLength() <= 1)
return;
for (char *fw = _cstr, *bw = _cstr + _len - 1;
*fw; ++fw, --bw) {
std::swap(*fw, *bw);
}
}
void String::SetAt(size_t index, char c) {
if (_cstr && index < GetLength() && c) {
BecomeUnique();
_cstr[index] = c;
}
}
void String::SetString(const char *cstr, size_t length) {
if (cstr) {
length = Math::Min(length, strlen(cstr));
if (length > 0) {
ReserveAndShift(false, Math::Surplus(length, GetLength()));
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 (!_cstr || !_len) {
return;
}
const char *trim_ptr = _cstr;
for (;;) {
auto t = *trim_ptr;
if (t == 0) {
break;
}
if (c && t != c) {
break;
}
if (!c && !isspace(t)) {
break;
}
trim_ptr++;
}
size_t trimmed = trim_ptr - _cstr;
if (trimmed > 0) {
BecomeUnique();
_len -= trimmed;
_cstr += trimmed;
}
}
void String::TrimRight(char c) {
if (!_cstr || !_len) {
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 && !isspace(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 (_cstr) {
count = Math::Min(count, _len);
if (count < _len) {
BecomeUnique();
_len = count;
_cstr[_len] = 0;
}
}
}
void String::TruncateToMid(size_t from, size_t count) {
if (_cstr) {
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 (_cstr) {
count = Math::Min(count, GetLength());
if (count < _len) {
BecomeUnique();
_cstr += _len - count;
_len = count;
}
}
}
void String::TruncateToLeftSection(char separator, bool exclude_separator) {
if (_cstr && separator) {
size_t slice_at = FindChar(separator);
if (slice_at != -1) {
TruncateToLeft(exclude_separator ? slice_at : slice_at + 1);
}
}
}
void String::TruncateToRightSection(char separator, bool exclude_separator) {
if (_cstr && separator) {
size_t slice_at = FindCharReverse(separator);
if (slice_at != -1) {
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 (!_cstr || !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 is NOT supposed to *modify* the const buffer.
// Any non-read operation on the buffer is preceded by a call to BecomeUnique,
// which in turn will allocate a reference-counted buffer copy.
_cstr = const_cast<char *>(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=(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) {
if (!_cstr) {
return;
}
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 = Math::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;
}
void String::BecomeUnique() {
if (_cstr && (_bufHead && _bufHead->RefCount > 1 || !_bufHead)) {
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) {
// grow by 50% or at least to total_size
size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity >> 1);
Copy(Math::Max(total_length, grow_length), left ? more_length : 0u);
} else if (_bufHead->RefCount > 1) {
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 {
Create(more_length);
}
}
} // namespace Common
} // namespace AGS