272 lines
7.3 KiB
C++
272 lines
7.3 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
|
|
// Exact tungsten idStr storage layout (tungsten.exe.h type 12142). This is a
|
|
// deliberately small ABI facade; more authoritative text methods will be
|
|
// added as text/str.cpp is reconstructed.
|
|
class idStr {
|
|
public:
|
|
idStr()
|
|
: len(0)
|
|
, data(baseBuffer)
|
|
, allocedAndFlag(20) {
|
|
baseBuffer[0] = '\0';
|
|
}
|
|
|
|
idStr(const char* text)
|
|
: idStr() {
|
|
Assign(text);
|
|
}
|
|
|
|
idStr(const idStr& other)
|
|
: idStr() {
|
|
Assign(other.c_str());
|
|
}
|
|
|
|
~idStr() {
|
|
if (data != baseBuffer && !IsStaticBuffer()) {
|
|
std::free(data);
|
|
}
|
|
}
|
|
|
|
idStr& operator=(const idStr& other) {
|
|
if (this != &other) {
|
|
Assign(other.c_str());
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
idStr& operator=(const char* text) {
|
|
Assign(text);
|
|
return *this;
|
|
}
|
|
|
|
const char* c_str() const {
|
|
return data;
|
|
}
|
|
|
|
operator const char*() const {
|
|
return data;
|
|
}
|
|
|
|
int Length() const {
|
|
return len;
|
|
}
|
|
|
|
void Clear() {
|
|
len = 0;
|
|
data[0] = '\0';
|
|
}
|
|
|
|
void TrimWhitespaceRecovered() {
|
|
while (len > 0
|
|
&& std::isspace(static_cast<unsigned char>(data[len - 1])) != 0) {
|
|
data[--len] = '\0';
|
|
}
|
|
int amount = 0;
|
|
while (amount < len
|
|
&& std::isspace(static_cast<unsigned char>(data[amount])) != 0) {
|
|
++amount;
|
|
}
|
|
if (amount > 0) {
|
|
std::memmove(data, data + amount,
|
|
static_cast<std::size_t>(len - amount + 1));
|
|
len -= amount;
|
|
}
|
|
}
|
|
|
|
// Recovered from shared/idlib/text/str.cpp. Resource names are lowercase,
|
|
// use forward slashes, and discard leading slashes except for UNC names.
|
|
void MakeNameCanonical() {
|
|
for (int index = 0; index < len; ++index) {
|
|
if (data[index] >= 'A' && data[index] <= 'Z') {
|
|
data[index] = static_cast<char>(data[index] + ('a' - 'A'));
|
|
} else if (data[index] == '\\') {
|
|
data[index] = '/';
|
|
}
|
|
}
|
|
if (!(len >= 2 && data[0] == '/' && data[1] == '/')) {
|
|
int leading = 0;
|
|
while (leading < len && data[leading] == '/') {
|
|
++leading;
|
|
}
|
|
if (leading > 0) {
|
|
std::memmove(data, data + leading,
|
|
static_cast<std::size_t>(len - leading + 1));
|
|
len -= leading;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Append(const char* text) {
|
|
if (text == nullptr || *text == '\0') {
|
|
return;
|
|
}
|
|
const int appendLength = static_cast<int>(std::strlen(text));
|
|
if (!EnsureAlloced(len + appendLength + 1)) {
|
|
return;
|
|
}
|
|
std::memcpy(data + len, text, static_cast<std::size_t>(appendLength + 1));
|
|
len += appendLength;
|
|
}
|
|
|
|
void Append(const char value) {
|
|
if (!EnsureAlloced(len + 2)) {
|
|
return;
|
|
}
|
|
data[len++] = value;
|
|
data[len] = '\0';
|
|
}
|
|
|
|
void Append(const idStr& text) {
|
|
Append(text.c_str());
|
|
}
|
|
|
|
idStr& StripFileExtension() {
|
|
for (int index = len - 1; index >= 0; --index) {
|
|
const char value = data[index];
|
|
if (value == '\\' || value == '/') {
|
|
break;
|
|
}
|
|
if (value == '.') {
|
|
data[index] = '\0';
|
|
len = index;
|
|
break;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
idStr& SetFileExtension(const char* extension) {
|
|
StripFileExtension();
|
|
if (extension != nullptr && extension[0] != '.') {
|
|
Append('.');
|
|
}
|
|
Append(extension);
|
|
return *this;
|
|
}
|
|
|
|
void Format(const char* format, ...) {
|
|
char buffer[4096];
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
const int count = _vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE,
|
|
format != nullptr ? format : "", arguments);
|
|
va_end(arguments);
|
|
if (count >= 0) Assign(buffer);
|
|
else Clear();
|
|
}
|
|
|
|
void ReplaceRecovered(const char* oldText, const char* newText) {
|
|
if (oldText == nullptr || oldText[0] == '\0') {
|
|
return;
|
|
}
|
|
const char* const replacement = newText == nullptr ? "" : newText;
|
|
const int oldLength = static_cast<int>(std::strlen(oldText));
|
|
idStr result;
|
|
const char* cursor = data;
|
|
for (;;) {
|
|
const char* const match = std::strstr(cursor, oldText);
|
|
if (match == nullptr) {
|
|
result.Append(cursor);
|
|
break;
|
|
}
|
|
const int prefixLength = static_cast<int>(match - cursor);
|
|
for (int index = 0; index < prefixLength; ++index) {
|
|
result.Append(cursor[index]);
|
|
}
|
|
result.Append(replacement);
|
|
cursor = match + oldLength;
|
|
}
|
|
Assign(result.c_str());
|
|
}
|
|
|
|
static int Cmp(const char* left, const char* right) {
|
|
const char* const safeLeft = left == nullptr ? "" : left;
|
|
const char* const safeRight = right == nullptr ? "" : right;
|
|
return std::strcmp(safeLeft, safeRight);
|
|
}
|
|
|
|
bool operator==(const idStr& other) const {
|
|
return Cmp(c_str(), other.c_str()) == 0;
|
|
}
|
|
|
|
bool operator!=(const idStr& other) const {
|
|
return !(*this == other);
|
|
}
|
|
|
|
bool operator<(const idStr& other) const {
|
|
return Cmp(c_str(), other.c_str()) < 0;
|
|
}
|
|
|
|
protected:
|
|
int len;
|
|
char* data;
|
|
int allocedAndFlag;
|
|
char baseBuffer[20];
|
|
|
|
void UseStaticBufferRecovered(char* buffer, const int capacity) {
|
|
if (data != baseBuffer && !IsStaticBuffer()) {
|
|
std::free(data);
|
|
}
|
|
data = buffer;
|
|
len = 0;
|
|
allocedAndFlag = capacity | static_cast<int>(0x80000000u);
|
|
if (data != nullptr && capacity > 0) {
|
|
data[0] = '\0';
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
int GetAlloced() const {
|
|
return allocedAndFlag & 0x7FFFFFFF;
|
|
}
|
|
|
|
bool IsStaticBuffer() const {
|
|
return (static_cast<unsigned int>(allocedAndFlag) & 0x80000000u) != 0;
|
|
}
|
|
|
|
bool EnsureAlloced(const int amount) {
|
|
if (amount <= GetAlloced()) {
|
|
return true;
|
|
}
|
|
if (IsStaticBuffer()) {
|
|
return false;
|
|
}
|
|
|
|
const int newAmount = (std::max)(amount, amount + amount / 2);
|
|
char* const replacement = static_cast<char*>(
|
|
std::malloc(static_cast<std::size_t>(newAmount))
|
|
);
|
|
if (replacement == nullptr) {
|
|
return false;
|
|
}
|
|
std::memcpy(replacement, data, static_cast<std::size_t>(len + 1));
|
|
if (data != baseBuffer) {
|
|
std::free(data);
|
|
}
|
|
data = replacement;
|
|
allocedAndFlag = newAmount;
|
|
return true;
|
|
}
|
|
|
|
void Assign(const char* text) {
|
|
const char* const source = text == nullptr ? "" : text;
|
|
const int sourceLength = static_cast<int>(std::strlen(source));
|
|
if (!EnsureAlloced(sourceLength + 1)) {
|
|
return;
|
|
}
|
|
std::memmove(data, source, static_cast<std::size_t>(sourceLength + 1));
|
|
len = sourceLength;
|
|
}
|
|
};
|
|
|
|
static_assert(sizeof(idStr) == 32, "Recovered idStr ABI changed");
|