A collection of basic/generally desirable code I use across multiple C++ projects.
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

#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