#pragma once #ifndef ULE_STRING_H #define ULE_STRING_H #include "config.h" #include "types.h" #include "alloc.h" #include // memcpy, memset, memcmp #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC #include #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 or 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