You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1016 lines
34 KiB
1016 lines
34 KiB
|
|
#pragma once
|
|
#ifndef ULE_STRING_H
|
|
#define ULE_STRING_H
|
|
|
|
#include "config.h"
|
|
#include "types.h"
|
|
#include "alloc.h"
|
|
|
|
#include <cstring> // memcpy, memset, memcmp
|
|
|
|
#define STB_SPRINTF_IMPLEMENTATION
|
|
#define STB_SPRINTF_STATIC
|
|
#include <stb/stb_sprintf.h>
|
|
|
|
#define STR_ASSERT assert
|
|
#define STR_IMPLEMENTATION
|
|
#define STR_SUPPORT_STD_STRING 0
|
|
#define STR_DEFINE_STR32 0 // the type Str32, which would normally be available, conflicts with a type in MacTypes.h
|
|
|
|
|
|
static Allocator* _stringClassAllocator = &Allocator::GetDefault(); // only used for the string class, string functions you pass an allocator if it allocates
|
|
|
|
// 'String' is a datatype, but it also is a namespace for a bunch of static 'char*' operations that
|
|
// you would normally find in the <cstring> or <string.h> header
|
|
// The datatype is a modified version of a string class developed by Omar Cornut: https://github.com/ocornut/str
|
|
class String {
|
|
public:
|
|
// Static empty buffer we can point to for empty strings
|
|
// Pointing to a literal increases the like-hood of getting a crash if someone attempts to write in the empty string buffer.
|
|
constexpr static char* EmptyBuffer = (char*) "\0NULL";
|
|
|
|
constexpr static unsigned char ASCII_LOWER[128] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
|
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
|
64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
|
|
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95,
|
|
96, 97, 98, 99, 100,101,102,103,104,105,106,107,108,109,110,111,
|
|
112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
|
|
};
|
|
constexpr static unsigned char ASCII_UPPER[128] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
|
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
|
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
|
|
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
|
|
96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
|
|
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127
|
|
};
|
|
|
|
static inline s32 sprintf(char* buffer, const char* format, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
s32 code = stbsp_vsprintf(buffer, format, args);
|
|
va_end(args);
|
|
|
|
return code;
|
|
}
|
|
static inline s32 snprintf(char* buffer, s32 count, const char* format, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
s32 code = stbsp_vsnprintf(buffer, count, format, args);
|
|
va_end(args);
|
|
|
|
return code;
|
|
}
|
|
static inline s32 vsnprintf(char* buffer, int count, const char* format, va_list args) {
|
|
return stbsp_vsnprintf(buffer, count, format, args);
|
|
}
|
|
|
|
static inline bool isDigit(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return (c >= '0') && (c <= '9');
|
|
}
|
|
static inline bool isAlpha(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return (c >= 'A' && c <= 'Z')
|
|
|| (c >= 'a' && c <= 'z');
|
|
}
|
|
static inline bool isHexDigit(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return ((c >= '0') && (c <= '9'))
|
|
|| ((c >= 'A') && (c <= 'F'))
|
|
|| ((c >= 'a') && (c <= 'f'));
|
|
}
|
|
static inline bool isOctalDigit(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return (c >= '0') && (c <= '7');
|
|
}
|
|
static inline bool isBinaryDigit(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return c == '0' || c == '1';
|
|
}
|
|
|
|
static inline char* intToString(u64 integer) { // @ALLOC
|
|
ULE_TYPES_H_FTAG;
|
|
u32 capacity = 10;
|
|
u32* remainders = (u32*) pMalloc(sizeof (u32) * capacity);
|
|
|
|
u32 count = 0;
|
|
while (true) {
|
|
if (capacity <= count) {
|
|
capacity *= 2;
|
|
remainders = (u32*) pRealloc(remainders, sizeof (u32) * capacity);
|
|
}
|
|
|
|
remainders[count++] = integer % 10;
|
|
integer /= 10;
|
|
if (integer == 0) break;
|
|
}
|
|
|
|
char* buffer = (char*) pMalloc(sizeof (char) * count + 1);
|
|
for (u32 i = 0; i < count; i++) {
|
|
buffer[count - i - 1] = '0' + remainders[i];
|
|
}
|
|
|
|
buffer[count] = '\0';
|
|
pFree(remainders);
|
|
return buffer;
|
|
}
|
|
|
|
static inline u64 hexStringToInt(const char* str) {
|
|
ULE_TYPES_H_FTAG;
|
|
u64 out = 0;
|
|
|
|
while (*str != '\0') {
|
|
u8 byte = *str++;
|
|
|
|
if ((byte >= '0') && (byte <= '9')) {
|
|
byte = byte - '0';
|
|
|
|
} else if ((byte >= 'a') && (byte <= 'f')) {
|
|
byte = byte - 'a' + 10;
|
|
|
|
} else if ((byte >= 'A') && (byte <= 'F')) {
|
|
byte = byte - 'A' + 10;
|
|
}
|
|
|
|
// only use the last four bits - precision of a single hex digit
|
|
out = (out << 4) | (byte & 0xF);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static inline u32 len(const char* string) {
|
|
ULE_TYPES_H_FTAG;
|
|
const char* start = string;
|
|
while (*string++ != '\0') {}
|
|
return (u32) (string - start);
|
|
}
|
|
|
|
// returns true if null-terminated strings |s1| and |s2| are equal
|
|
static inline bool eq(const char* s1, const char* s2) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 l1 = String::len(s1);
|
|
u32 l2 = String::len(s2);
|
|
|
|
if (l1 != l2) return false;
|
|
|
|
for (u32 i = 0; i < l1; i++) {
|
|
if (s1[i] != s2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// same as |eq|, but handles |s1| and/or |s2| being null
|
|
static inline bool eqNullCheck(const char* s1, const char* s2) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (s1 == null) {
|
|
if (s2 == null) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (s2 == null) {
|
|
return false;
|
|
}
|
|
|
|
return String::eq(s1, s2);
|
|
}
|
|
|
|
// heap allocates a copy of |string| and returns a pointer to it.
|
|
static inline char* cpy(const char* string, u32 length, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
assert(&allocator == &Allocator::GetDefault());
|
|
char* buffer = (char*) pMalloc(sizeof(char) * (length+1)); //allocator.mallocate(sizeof (char) * (length + 1), allocator.state);
|
|
|
|
u32 i = 0;
|
|
for (; i < length; i++) {
|
|
buffer[i] = string[i];
|
|
}
|
|
buffer[i] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// heap allocates a copy of |string| and returns a pointer to it.
|
|
static inline char* cpy(const char* string, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 len = String::len(string);
|
|
return String::cpy(string, len, allocator = Allocator::GetDefault());
|
|
}
|
|
|
|
static inline bool memeq(const unsigned char* m1, const unsigned char* m2, size_t length) {
|
|
ULE_TYPES_H_FTAG;
|
|
return memcmp(m1, m2, length) == 0;
|
|
}
|
|
static inline bool memeq(const unsigned char* m1, size_t l1, const unsigned char* m2, size_t l2) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (l1 != l2) return false;
|
|
|
|
return memeq(m1, m2, l1);
|
|
}
|
|
|
|
// assumes null termination.
|
|
static inline bool LexographicComparisonASCII(const char* str1, const char* str2) {
|
|
u32 min;
|
|
|
|
const u32 l1 = String::len(str1);
|
|
const u32 l2 = String::len(str2);
|
|
if (l1 > l2) {
|
|
min = l2;
|
|
} else {
|
|
min = l1;
|
|
}
|
|
|
|
for (u32 i = 0; i < min; i++) {
|
|
if (str1[i] < str2[i]) {
|
|
return true;
|
|
|
|
} else if (str1[i] > str2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return l1 < l2;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
static inline size_t wcharToChar(wchar_t* wstring, char* buffer, size_t maxBufferLength) {
|
|
ULE_TYPES_H_FTAG;
|
|
return wcstombs(buffer, wstring, maxBufferLength);
|
|
}
|
|
#endif
|
|
|
|
static inline void* memset(void* p, char c, u32 length) {
|
|
ULE_TYPES_H_FTAG;
|
|
//__stosb((unsigned char*) p, c, length);
|
|
char* a = (char*) p;
|
|
for (u32 i = 0; i < length; i++) a[i] = c;
|
|
return a;
|
|
}
|
|
|
|
static inline void memcpy(void* dest, void* src, u32 size) {
|
|
ULE_TYPES_H_FTAG;
|
|
// allowing c++ compilers to know we're invoking memcpy allows more aggressive optimizations, sometimes resulting in 8-9x speedup,
|
|
// when compared to the horrible, commented out loop below.
|
|
std::memcpy(dest, src, size);
|
|
|
|
//u8* dest_ = (u8*) dest;
|
|
//u8* src_ = (u8*) src;
|
|
|
|
//for (u32 i = 0; i < size; i++) {
|
|
// dest_[i] = src_[i];
|
|
//}
|
|
}
|
|
|
|
// replace all instances of |c1| in |string| with |c2|
|
|
static inline void replaceC(char* string, u32 length, char c1, char c2) {
|
|
ULE_TYPES_H_FTAG;
|
|
for (u32 i = 0; i < length; i++) {
|
|
if (string[i] == c1) {
|
|
string[i] = c2;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline const char* firstCharOccurence(const char* string, u32 length, char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
for (u32 i = 0; i < length; i++) {
|
|
const char* s = string + i;
|
|
if (*s == c) {
|
|
return s;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static inline const char* firstCharOccurence(const char* string, char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return String::firstCharOccurence(string, String::len(string), c);
|
|
}
|
|
|
|
static inline const char* lastCharOccurence(const char* string, u32 length, char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
for (s32 i = length - 1; i >= 0; i--) { // @NOTE 'i' needs to be a signed int here...
|
|
if (*(string + i) == c) {
|
|
return string + i;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static inline const char* lastCharOccurence(const char* string, char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
return String::lastCharOccurence(string, String::len(string), c);
|
|
}
|
|
|
|
static inline bool hasSuffix(const char* string, const char* suffix) {
|
|
ULE_TYPES_H_FTAG;
|
|
const char* p = String::lastCharOccurence(string, String::len(string), suffix[0]);
|
|
if (p) return String::eq(p, suffix);
|
|
return false;
|
|
}
|
|
|
|
static inline u32 countLines(const char* buffer) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 lines = 0;
|
|
char c;
|
|
|
|
while ((c = *buffer) != '\0') {
|
|
if (c == '\n') lines++;
|
|
buffer++;
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
static inline bool isAscii(const char* buffer, u32 length) {
|
|
ULE_TYPES_H_FTAG;
|
|
const unsigned char* ubuffer = (const unsigned char*) buffer;
|
|
for (u32 i = 0; i < length; i++) {
|
|
if (ubuffer[i] & 128) { // binary: 0b 1000 0000
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline bool isAsciiWhitespace(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
switch (c) {
|
|
//case '\b':
|
|
//case '\v':
|
|
//case '\f':
|
|
|
|
case '\r':
|
|
case '\t':
|
|
case '\n':
|
|
case ' ':
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
// static inline bool isUnicodeSpaceSeparator(wide character);
|
|
|
|
// @TODO ALL OF THESE TRIMS
|
|
//static inline char* trimStart(const char* str, u32 count);
|
|
//static inline char* trimEnd(const char* str, u32 count);
|
|
static inline char* trim(const char* str, u32 count, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 length = String::len(str);
|
|
|
|
if (length <= count) {
|
|
return (char*) "";
|
|
}
|
|
|
|
char* buffer = (char*) allocator.mallocate(sizeof (char) * (length - 1), allocator.state);
|
|
|
|
u32 i = 0;
|
|
for (; i < (length - count); i++) {
|
|
buffer[i] = str[i + 1];
|
|
}
|
|
|
|
buffer[i] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static inline char* asciiToLower(const char* str, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 length = String::len(str);
|
|
char* buffer = (char*) allocator.mallocate(sizeof (char) * length + 1, allocator.state);
|
|
u32 i = 0;
|
|
for (; i < length; i++) {
|
|
buffer[i] = String::ASCII_LOWER[str[i]];
|
|
}
|
|
buffer[i] = '\0';
|
|
return buffer;
|
|
}
|
|
|
|
static inline char* asciiToUpper(const char* str, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 length = String::len(str);
|
|
char* buffer = (char*) allocator.mallocate(sizeof (char) * length + 1, allocator.state);
|
|
u32 i = 0;
|
|
for (; i < length; i++) {
|
|
buffer[i] = String::ASCII_LOWER[str[i]];
|
|
}
|
|
buffer[i] = '\0';
|
|
return buffer;
|
|
}
|
|
|
|
static inline char* concat(const char* str1, const char* str2, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 l1 = String::len(str1);
|
|
u32 l2 = String::len(str2);
|
|
u32 newLength = l1 + l2;
|
|
char* newBuffer = (char*) allocator.mallocate(sizeof (char) * newLength + 1, allocator.state);
|
|
u32 i = 0;
|
|
for (; i < newLength; i++) {
|
|
if (i < l1) {
|
|
newBuffer[i] = str1[i];
|
|
} else {
|
|
newBuffer[i] = str2[i - l1];
|
|
}
|
|
}
|
|
newBuffer[i] = '\0';
|
|
return newBuffer;
|
|
}
|
|
|
|
static inline u32 write(char* dest, const char* src, u32 length) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 i = 0;
|
|
for (; i < length; i++) {
|
|
dest[i] = src[i];
|
|
}
|
|
dest[i] = '\0';
|
|
return i;
|
|
}
|
|
|
|
// returns the number of characters written.
|
|
static inline u32 write(char* dest, const char* src) {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 length = String::len(src);
|
|
return String::write(dest, src, length);
|
|
}
|
|
|
|
static inline char* read(const char* buffer, u32 length, Allocator& allocator = Allocator::GetDefault()) {
|
|
ULE_TYPES_H_FTAG;
|
|
char* tk = (char*) allocator.mallocate(sizeof (char) * length + 1, allocator.state);
|
|
u32 i = 0;
|
|
while (i < length) {
|
|
tk[i] = *(buffer + i);
|
|
i++;
|
|
}
|
|
tk[i] = '\0';
|
|
return tk;
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Begin String class type
|
|
static inline void SetStringClassAllocator(Allocator* allocator) {
|
|
_stringClassAllocator = allocator;
|
|
}
|
|
|
|
char* Data; // Point to LocalBuf() or heap allocated
|
|
int Capacity : 21; // Max 2 MB
|
|
int LocalBufSize : 10; // Max 1023 bytes
|
|
unsigned int Owned : 1; // Set when we have ownership of the pointed data (most common, unless using set_ref() method or StringRef constructor)
|
|
|
|
inline char* c_str() { ULE_TYPES_H_FTAG; return Data; }
|
|
inline const char* c_str() const { ULE_TYPES_H_FTAG; return Data; }
|
|
inline bool empty() const { ULE_TYPES_H_FTAG; return Data[0] == 0; }
|
|
inline int length() const { ULE_TYPES_H_FTAG; return (int)strlen(Data); } // by design, allow user to write into the buffer at any time
|
|
inline int capacity() const { ULE_TYPES_H_FTAG; return Capacity; }
|
|
inline bool owned() const { ULE_TYPES_H_FTAG; return Owned ? true : false; }
|
|
|
|
inline char& operator[](size_t i) { ULE_TYPES_H_FTAG; return Data[i]; }
|
|
inline char operator[](size_t i) const { ULE_TYPES_H_FTAG; return Data[i]; }
|
|
inline String& operator=(const String& rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; }
|
|
inline bool operator==(const String& rhs) const { ULE_TYPES_H_FTAG; return strcmp(c_str(), rhs.c_str()) == 0; }
|
|
inline String& operator=(const char* rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; }
|
|
inline bool operator==(const char* rhs) const { ULE_TYPES_H_FTAG; return strcmp(c_str(), rhs) == 0; }
|
|
|
|
inline String() {
|
|
ULE_TYPES_H_FTAG;
|
|
Data = EmptyBuffer; // Shared READ-ONLY initial buffer for 0 capacity
|
|
Capacity = 0;
|
|
LocalBufSize = 0;
|
|
Owned = 0;
|
|
}
|
|
inline String(const String& rhs) : String() {
|
|
ULE_TYPES_H_FTAG;
|
|
set(rhs);
|
|
}
|
|
inline String(const char* rhs) : String() {
|
|
ULE_TYPES_H_FTAG;
|
|
set(rhs);
|
|
}
|
|
|
|
inline void set_ref(const char* src) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (Owned && !is_using_local_buf())
|
|
_stringClassAllocator->free(Data, null);
|
|
Data = src ? (char*)src : EmptyBuffer;
|
|
Capacity = 0;
|
|
Owned = 0;
|
|
}
|
|
inline void set(const String& src) {
|
|
ULE_TYPES_H_FTAG;
|
|
int buf_len = (int)strlen(src.c_str())+1;
|
|
if ((int)Capacity < buf_len)
|
|
reserve_discard(buf_len);
|
|
memcpy(Data, (void*)src.c_str(), (size_t)buf_len);
|
|
Owned = 1;
|
|
}
|
|
|
|
inline void set(const char* src) {
|
|
ULE_TYPES_H_FTAG;
|
|
// We allow set(NULL) or via = operator to clear the string.
|
|
if (src == NULL)
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
int buf_len = (int)strlen(src)+1;
|
|
if (Capacity < buf_len)
|
|
reserve_discard(buf_len);
|
|
memcpy(Data, (void*)src, (size_t)buf_len);
|
|
Owned = 1;
|
|
}
|
|
|
|
inline void set(const char* src, const char* src_end) {
|
|
ULE_TYPES_H_FTAG;
|
|
STR_ASSERT(src != NULL && src_end >= src);
|
|
int buf_len = (int)(src_end-src)+1;
|
|
if ((int)Capacity < buf_len)
|
|
reserve_discard(buf_len);
|
|
memcpy(Data, (void*)src, (size_t)(buf_len - 1));
|
|
Data[buf_len-1] = 0;
|
|
Owned = 1;
|
|
}
|
|
|
|
// Clear
|
|
inline void clear() {
|
|
ULE_TYPES_H_FTAG;
|
|
if (Owned && !is_using_local_buf())
|
|
_stringClassAllocator->free(Data, null);
|
|
if (LocalBufSize) {
|
|
Data = local_buf();
|
|
Data[0] = '\0';
|
|
Capacity = LocalBufSize;
|
|
Owned = 1;
|
|
} else {
|
|
Data = EmptyBuffer;
|
|
Capacity = 0;
|
|
Owned = 0;
|
|
}
|
|
}
|
|
|
|
// Reserve memory, preserving the current of the buffer
|
|
inline void reserve(int new_capacity) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (new_capacity <= Capacity)
|
|
return;
|
|
|
|
char* new_data;
|
|
if (new_capacity < LocalBufSize) {
|
|
// Disowned -> LocalBuf
|
|
new_data = local_buf();
|
|
new_capacity = LocalBufSize;
|
|
} else {
|
|
// Disowned or LocalBuf -> Heap
|
|
new_data = (char*)_stringClassAllocator->mallocate((size_t)new_capacity * sizeof(char), null);
|
|
}
|
|
|
|
// string in Data might be longer than new_capacity if it wasn't owned, don't copy too much
|
|
#ifdef _MSC_VER
|
|
strncpy_s(new_data, (size_t)new_capacity, Data, (size_t)new_capacity - 1);
|
|
#else
|
|
strncpy(new_data, Data, (size_t)new_capacity - 1);
|
|
#endif
|
|
new_data[new_capacity - 1] = 0;
|
|
|
|
if (Owned && !is_using_local_buf())
|
|
_stringClassAllocator->free(Data, null);
|
|
|
|
Data = new_data;
|
|
Capacity = new_capacity;
|
|
Owned = 1;
|
|
}
|
|
|
|
// Reserve memory, discarding the current of the buffer (if we expect to be fully rewritten)
|
|
inline void reserve_discard(int new_capacity) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (new_capacity <= Capacity)
|
|
return;
|
|
|
|
if (Owned && !is_using_local_buf())
|
|
_stringClassAllocator->free(Data, null);
|
|
|
|
if (new_capacity < LocalBufSize) {
|
|
// Disowned -> LocalBuf
|
|
Data = local_buf();
|
|
Capacity = LocalBufSize;
|
|
} else {
|
|
// Disowned or LocalBuf -> Heap
|
|
Data = (char*)_stringClassAllocator->mallocate((size_t)new_capacity * sizeof(char), null);
|
|
Capacity = new_capacity;
|
|
}
|
|
Owned = 1;
|
|
}
|
|
|
|
inline void shrink_to_fit() {
|
|
ULE_TYPES_H_FTAG;
|
|
if (!Owned || is_using_local_buf()) return;
|
|
int new_capacity = length() + 1;
|
|
if (Capacity <= new_capacity) return;
|
|
|
|
char* new_data = (char*)_stringClassAllocator->mallocate((size_t)new_capacity * sizeof(char), null);
|
|
memcpy(new_data, Data, (size_t)new_capacity);
|
|
_stringClassAllocator->free(Data, null);
|
|
Data = new_data;
|
|
Capacity = new_capacity;
|
|
}
|
|
|
|
// FIXME: merge setfv() and appendfv()?
|
|
inline int setfv(const char* fmt, va_list args) {
|
|
ULE_TYPES_H_FTAG;
|
|
// Needed for portability on platforms where va_list are passed by reference and modified by functions
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
|
|
// First try
|
|
int len = vsnprintf(Owned ? Data : NULL, Owned ? (size_t)Capacity : 0, fmt, args);
|
|
STR_ASSERT(len >= 0);
|
|
|
|
if (Capacity < len + 1)
|
|
{
|
|
reserve_discard(len + 1);
|
|
len = vsnprintf(Data, (size_t)len + 1, fmt, args2);
|
|
}
|
|
|
|
STR_ASSERT(Owned);
|
|
return len;
|
|
}
|
|
|
|
inline int setf(const char* fmt, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
int len = setfv(fmt, args);
|
|
va_end(args);
|
|
return len;
|
|
}
|
|
|
|
inline int setfv_nogrow(const char* fmt, va_list args) {
|
|
ULE_TYPES_H_FTAG;
|
|
STR_ASSERT(Owned);
|
|
|
|
if (Capacity == 0) return 0;
|
|
|
|
int w = vsnprintf(Data, (size_t)Capacity, fmt, args);
|
|
Data[Capacity - 1] = 0;
|
|
Owned = 1;
|
|
return (w == -1) ? Capacity - 1 : w;
|
|
}
|
|
|
|
inline int setf_nogrow(const char* fmt, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
int len = setfv_nogrow(fmt, args);
|
|
va_end(args);
|
|
return len;
|
|
}
|
|
|
|
inline int append_from(int idx, char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
int add_len = 1;
|
|
if (Capacity < idx + add_len + 1)
|
|
reserve(idx + add_len + 1);
|
|
Data[idx] = c;
|
|
Data[idx + add_len] = 0;
|
|
STR_ASSERT(Owned);
|
|
return add_len;
|
|
}
|
|
|
|
inline int append_from(int idx, const char* s, const char* s_end) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (!s_end) s_end = s + strlen(s);
|
|
int add_len = (int)(s_end - s);
|
|
if (Capacity < idx + add_len + 1) reserve(idx + add_len + 1);
|
|
memcpy(Data + idx, (void*)s, (size_t)add_len);
|
|
Data[idx + add_len] = 0; // Our source data isn't necessarily zero-terminated
|
|
STR_ASSERT(Owned);
|
|
return add_len;
|
|
}
|
|
|
|
// FIXME: merge setfv() and appendfv()?
|
|
inline int appendfv_from(int idx, const char* fmt, va_list args) {
|
|
ULE_TYPES_H_FTAG;
|
|
// Needed for portability on platforms where va_list are passed by reference and modified by functions
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
|
|
// First try
|
|
int add_len = vsnprintf(Owned ? Data + idx : NULL, Owned ? (size_t)(Capacity - idx) : 0, fmt, args);
|
|
STR_ASSERT(add_len >= 0);
|
|
|
|
if (Capacity < idx + add_len + 1) {
|
|
reserve(idx + add_len + 1);
|
|
add_len = vsnprintf(Data + idx, (size_t)add_len + 1, fmt, args2);
|
|
}
|
|
|
|
STR_ASSERT(Owned);
|
|
return add_len;
|
|
}
|
|
|
|
inline int appendf_from(int idx, const char* fmt, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
int len = appendfv_from(idx, fmt, args);
|
|
va_end(args);
|
|
return len;
|
|
}
|
|
|
|
inline int append(char c) {
|
|
ULE_TYPES_H_FTAG;
|
|
int cur_len = length();
|
|
return append_from(cur_len, c);
|
|
}
|
|
|
|
inline int append(const char* s, const char* s_end = null) {
|
|
ULE_TYPES_H_FTAG;
|
|
int cur_len = length();
|
|
return append_from(cur_len, s, s_end);
|
|
}
|
|
|
|
inline int appendfv(const char* fmt, va_list args) {
|
|
ULE_TYPES_H_FTAG;
|
|
int cur_len = length();
|
|
return appendfv_from(cur_len, fmt, args);
|
|
}
|
|
|
|
int appendf(const char* fmt, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
int len = appendfv(fmt, args);
|
|
va_end(args);
|
|
return len;
|
|
}
|
|
|
|
// Destructor for all variants
|
|
inline ~String()
|
|
{
|
|
if (Owned && !is_using_local_buf())
|
|
if (_stringClassAllocator->free != null) ::_stringClassAllocator->free(Data, null);
|
|
}
|
|
|
|
protected:
|
|
inline char* local_buf() { ULE_TYPES_H_FTAG; return (char*)this + sizeof(String); }
|
|
inline const char* local_buf() const { ULE_TYPES_H_FTAG; return (char*)this + sizeof(String); }
|
|
inline bool is_using_local_buf() const { ULE_TYPES_H_FTAG; return Data == local_buf() && LocalBufSize != 0; }
|
|
|
|
// Constructor for StringXXX variants with local buffer
|
|
String(unsigned short local_buf_size) {
|
|
ULE_TYPES_H_FTAG;
|
|
STR_ASSERT(local_buf_size < 1024);
|
|
Data = local_buf();
|
|
Data[0] = '\0';
|
|
Capacity = local_buf_size;
|
|
LocalBufSize = local_buf_size;
|
|
Owned = 1;
|
|
}
|
|
};
|
|
|
|
// Literal/reference string
|
|
class StringRef : public String {
|
|
public:
|
|
StringRef(const char* s) : String() { ULE_TYPES_H_FTAG; set_ref(s); }
|
|
};
|
|
|
|
// Types embedding a local buffer
|
|
// NB: we need to override the constructor and = operator for both String& and TYPENAME (without the later compiler will call a default copy operator)
|
|
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
|
|
class TYPENAME : public String \
|
|
{ \
|
|
char local_buf[LOCALBUFSIZE]; \
|
|
public: \
|
|
TYPENAME() : String(LOCALBUFSIZE) {} \
|
|
TYPENAME(const String& rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME(const char* rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME(const TYPENAME& rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME& operator=(const char* rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
TYPENAME& operator=(const String& rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
TYPENAME& operator=(const TYPENAME& rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
};
|
|
|
|
// Disable PVS-Studio warning V730: Not all members of a class are initialized inside the constructor (local_buf is not initialized and that is fine)
|
|
// -V:STR_DEFINETYPE:730
|
|
|
|
// Helper to define StringXXXf constructors
|
|
#define STR_DEFINETYPE_F(TYPENAME, TYPENAME_F) \
|
|
class TYPENAME_F : public TYPENAME \
|
|
{ \
|
|
public: \
|
|
TYPENAME_F(const char* fmt, ...) : TYPENAME() { ULE_TYPES_H_FTAG; va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
|
|
};
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunused-private-field" // warning : private field 'local_buf' is not used
|
|
#endif
|
|
|
|
// Declaring types for common sizes here
|
|
STR_DEFINETYPE(String16, 16)
|
|
STR_DEFINETYPE(String30, 30)
|
|
STR_DEFINETYPE(String64, 64)
|
|
STR_DEFINETYPE(String128, 128)
|
|
STR_DEFINETYPE(String256, 256)
|
|
STR_DEFINETYPE(String512, 512)
|
|
|
|
// Declaring helper constructors to pass in format strings in one statement
|
|
STR_DEFINETYPE_F(String16, String16f)
|
|
STR_DEFINETYPE_F(String30, String30f)
|
|
STR_DEFINETYPE_F(String64, String64f)
|
|
STR_DEFINETYPE_F(String128, String128f)
|
|
STR_DEFINETYPE_F(String256, String256f)
|
|
STR_DEFINETYPE_F(String512, String512f)
|
|
|
|
#if STR_DEFINE_STR32
|
|
STR_DEFINETYPE(String32, 32)
|
|
STR_DEFINETYPE_F(String32, String32f)
|
|
#endif
|
|
|
|
// by default, if you use a string type with a local buffer, and append to it with something bigger than the local buffer,
|
|
// you end up realloc'ing space to fit, and this reallocation is stingy - only exactly as much as needed.
|
|
// if you are creating a string which may be repeatedly concatenated with other strings/appended to, it's better
|
|
// to do a smaller number of growing allocations along the lines of what dynamic arrays do.
|
|
// this type is the same as other string types, but if it has to reserve additional space for itself, it will do so
|
|
// eagerly, at a rate of 1.5x
|
|
#define STRINGBUFFER_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
|
|
class TYPENAME : public String { \
|
|
char local_buf[LOCALBUFSIZE]; \
|
|
public: \
|
|
TYPENAME(const char* fmt, ...) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
|
|
TYPENAME() : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; } \
|
|
TYPENAME(const String& rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME(const char* rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME(const TYPENAME& rhs) : String(LOCALBUFSIZE) { ULE_TYPES_H_FTAG; set(rhs); } \
|
|
TYPENAME& operator=(const char* rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
TYPENAME& operator=(const String& rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
TYPENAME& operator=(const TYPENAME& rhs) { ULE_TYPES_H_FTAG; set(rhs); return *this; } \
|
|
void reserve(int new_capacity) { \
|
|
ULE_TYPES_H_FTAG; \
|
|
if (new_capacity <= Capacity) \
|
|
return; \
|
|
char* new_data; \
|
|
if (new_capacity < LocalBufSize) { \
|
|
new_data = (char*)this + sizeof(String); \
|
|
new_capacity = LocalBufSize; \
|
|
} else { \
|
|
new_data = (char*)_stringClassAllocator->mallocate((size_t)new_capacity * sizeof(char), null); \
|
|
} \
|
|
strncpy(new_data, Data, (size_t)new_capacity - 1); \
|
|
new_data[new_capacity - 1] = 0; \
|
|
if (Owned && !is_using_local_buf()) \
|
|
_stringClassAllocator->free(Data, null); \
|
|
Data = new_data; \
|
|
Capacity = new_capacity; \
|
|
Owned = 1; \
|
|
} \
|
|
void reserve_discard(int new_capacity) { \
|
|
ULE_TYPES_H_FTAG; \
|
|
if (new_capacity <= Capacity) \
|
|
return; \
|
|
if (Owned && !is_using_local_buf()) \
|
|
_stringClassAllocator->free(Data, null); \
|
|
if (new_capacity < LocalBufSize) { \
|
|
Data = (char*)this + sizeof(String); \
|
|
Capacity = LocalBufSize; \
|
|
} else { \
|
|
while (Capacity < new_capacity) { \
|
|
Capacity = (s32)(Capacity * 1.5f); \
|
|
Data = (char*) _stringClassAllocator->mallocate((size_t) Capacity * sizeof(char), null); \
|
|
} \
|
|
} \
|
|
Owned = 1; \
|
|
} \
|
|
};
|
|
|
|
STRINGBUFFER_DEFINETYPE(StringBuffer512, 512)
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
// On some platform vsnprintf() takes va_list by reference and modifies it.
|
|
// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
|
|
#ifndef va_copy
|
|
#define va_copy(dest, src) (dest = src)
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
// https://www.fileformat.info/info/unicode/category/Zs/list.htm
|
|
/* @TODO
|
|
bool isUnicodeSpaceSeparator(char c) {
|
|
switch (c) {
|
|
case 0x20:
|
|
case 0xA0:
|
|
case 0x1680:
|
|
case 0x2000:
|
|
case 0x2001:
|
|
case 0x2002:
|
|
case 0x2003:
|
|
case 0x2004:
|
|
case 0x2005:
|
|
case 0x2006:
|
|
case 0x2007:
|
|
case 0x2008:
|
|
case 0x2009:
|
|
case 0x200A:
|
|
case 0x202F:
|
|
case 0x205F:
|
|
case 0x3000:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
//struct StringBuffer {
|
|
// u32 length;
|
|
// u32 capacity;
|
|
// char* data;
|
|
//
|
|
// StringBuffer(u32 initialSize = 2048);
|
|
// StringBuffer(const char* string);
|
|
// ~StringBuffer();
|
|
//
|
|
// void checkIfShouldGrow();
|
|
// bool isEmpty() const;
|
|
// bool isFull() const;
|
|
// char pop();
|
|
// u32 append(char e);
|
|
//};
|
|
////================================================================================
|
|
//StringBuffer::StringBuffer(u32 initialSize) {
|
|
// ULE_TYPES_H_FTAG;
|
|
// this->length = 0;
|
|
// this->capacity = initialSize;
|
|
// this->data = (char*) pMalloc(sizeof(char) * this->capacity);
|
|
//}
|
|
//
|
|
//StringBuffer::StringBuffer(const char* string) {
|
|
// this->length = String::len(string);
|
|
// this->capacity = this->length;
|
|
// this->data = cpy(string, this->length);
|
|
//}
|
|
//
|
|
//StringBuffer::~StringBuffer() {
|
|
// pFree(this->data);
|
|
//}
|
|
//
|
|
//void StringBuffer::checkIfShouldGrow() {
|
|
// ULE_TYPES_H_FTAG;
|
|
// if (this->isFull()) {
|
|
// // optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
|
|
// // more testing is probably needed
|
|
// this->capacity = (u32) (this->capacity * 1.5);
|
|
// this->data = (char*) pRealloc(this->data, sizeof(char) * this->capacity);
|
|
// }
|
|
//}
|
|
//
|
|
//bool StringBuffer::isEmpty() const {
|
|
// ULE_TYPES_H_FTAG;
|
|
// return this->length == 0;
|
|
//}
|
|
//
|
|
//bool StringBuffer::isFull() const {
|
|
// ULE_TYPES_H_FTAG;
|
|
// return this->length == this->capacity;
|
|
//}
|
|
//
|
|
//char StringBuffer::pop() {
|
|
// ULE_TYPES_H_FTAG;
|
|
// if (this->isEmpty()) {
|
|
// die("empty");
|
|
// }
|
|
//
|
|
// return this->data[--this->length];
|
|
//}
|
|
//
|
|
//u32 StringBuffer::append(char e) {
|
|
// ULE_TYPES_H_FTAG;
|
|
// this->checkIfShouldGrow();
|
|
//
|
|
// this->data[this->length++] = e;
|
|
//
|
|
// return this->length - 1;
|
|
//}
|
|
|
|
#endif
|
|
|