|
|
#pragma once
#ifndef ULE_STRING_H
#define ULE_STRING_H
#include "config.h"
#include "types.h"
#include "alloc.h"
#include <string.h> // @TODO remove this
#define STB_SPRINTF_IMPLEMENTATION
#define STB_SPRINTF_STATIC
#include <stb/stb_sprintf.h>
#define STR_MEMALLOC pMalloc
#define STR_MEMFREE pFree
#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
// '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) { 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; char* buffer = (char*) 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); }
#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; 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; }
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()) STR_MEMFREE(Data); 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()) STR_MEMFREE(Data); 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*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); }
// 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()) STR_MEMFREE(Data);
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()) STR_MEMFREE(Data);
if (new_capacity < LocalBufSize) { // Disowned -> LocalBuf
Data = local_buf(); Capacity = LocalBufSize; } else { // Disowned or LocalBuf -> Heap
Data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); 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*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); memcpy(new_data, Data, (size_t)new_capacity); STR_MEMFREE(Data); 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()) STR_MEMFREE(Data); }
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*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); \ } \ strncpy(new_data, Data, (size_t)new_capacity - 1); \ new_data[new_capacity - 1] = 0; \ if (Owned && !is_using_local_buf()) \ STR_MEMFREE(Data); \ 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()) \ STR_MEMFREE(Data); \ if (new_capacity < LocalBufSize) { \ Data = (char*)this + sizeof(String); \ Capacity = LocalBufSize; \ } else { \ while (Capacity < new_capacity) { \ Capacity = (s32)(Capacity * 1.5f); \ Data = (char*) STR_MEMALLOC((size_t) Capacity * sizeof(char)); \ } \ } \ 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
|