diff --git a/CMakeLists.txt b/CMakeLists.txt index 740599dba4..ab37635334 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ endif() PROJECT(cr3) -set(CMAKE_CXX_STANDARD 17`) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/") @@ -83,6 +83,16 @@ ADD_DEFINITIONS( -DUSE_EXTERNAL_EDICT_DICTIONARY=${USE_EXTERNAL_EDICT_DICTIONARY # Document buffer cache / compression settings +#own memory manager +if (NOT DEFINED LDOM_USE_OWN_MEM_MAN) + SET(LDOM_USE_OWN_MEM_MAN 0) +else() + SET(LDOM_USE_OWN_MEM_MAN ${LDOM_USE_OWN_MEM_MAN}) +endif(NOT DEFINED LDOM_USE_OWN_MEM_MAN) +message("using LDOM_USE_OWN_MEM_MAN=${LDOM_USE_OWN_MEM_MAN}") +ADD_DEFINITIONS( -DLDOM_USE_OWN_MEM_MAN=${LDOM_USE_OWN_MEM_MAN} ) + + #whether to compress data if (NOT DEFINED DOC_DATA_COMPRESSION_LEVEL) message("-D DOC_DATA_COMPRESSION_LEVEL=0|1|2|3|4|5 parameter is not defined: will use default value") @@ -147,7 +157,7 @@ endif (NOT DEFINED ENABLE_DBUS_VIEWER_EVENTS) SET (CRGUI_DEFS -DCOLOR_BACKBUFFER=0 -DLDOM_USE_OWN_MEM_MAN=0 -DGRAY_BACKBUFFER_BITS=${GRAY_BACKBUFFER_BITS} -DENABLE_DBUS_VIEWER_EVENTS=${ENABLE_DBUS_VIEWER_EVENTS}) -SET (DESKTOP_DEFS -DCOLOR_BACKBUFFER=1 -DLDOM_USE_OWN_MEM_MAN=1 -DUSE_FREETYPE=1 ) +SET (DESKTOP_DEFS -DCOLOR_BACKBUFFER=1 -DLDOM_USE_OWN_MEM_MAN=0 -DUSE_FREETYPE=1 ) # Uncomment to test e-ink style color palette on Desktop #SET (DESKTOP_DEFS -DCOLOR_BACKBUFFER=0 -DGRAY_BACKBUFFER_BITS=4 -DLDOM_USE_OWN_MEM_MAN=1 -DUSE_FREETYPE=1 ) @@ -167,6 +177,7 @@ if( ${GUI} STREQUAL QT5 ) add_definitions(${Qt5Core_DEFINITIONS}) endif( ${GUI} STREQUAL QT5 ) + # thirdparty sources definitions include("${CMAKE_SOURCE_DIR}/thirdparty_repo/repo_srcdirs.cmake") @@ -480,6 +491,11 @@ elseif ( ${GUI} STREQUAL QT6 ) ADD_DEFINITIONS( ${DESKTOP_DEFS} ) ADD_SUBDIRECTORY(crengine) ADD_SUBDIRECTORY(cr3qt) +elseif ( ${GUI} STREQUAL CR3TEST ) + message("Will make CR3TEST benchmarks") + #ADD_DEFINITIONS( ${DESKTOP_DEFS} ) + ADD_SUBDIRECTORY(crengine) + ADD_SUBDIRECTORY(tests) elseif ( ${GUI} STREQUAL WX ) message("Will make CR3/WX") ADD_DEFINITIONS( ${DESKTOP_DEFS} -DCR_WX_SUPPORT=1 ) diff --git a/crengine/include/lvstring.h b/crengine/include/lvstring.h index 7f9f060b71..2726fb818e 100644 --- a/crengine/include/lvstring.h +++ b/crengine/include/lvstring.h @@ -38,6 +38,7 @@ #include #include "lvtypes.h" #include "lvmemman.h" +#include "utility" // (Note: some of these 0x have lowercase hex digit, to avoid // 'redefined' warnings as they are already defined in lowercase @@ -297,6 +298,10 @@ namespace fmt { }; } +#if LDOM_USE_OWN_MEM_MAN==1 + /// returns true if string chunk storage has been destroyed + bool ls_storage_is_destroyed(); +#endif /** \brief lChar8 string @@ -312,8 +317,8 @@ class lString8 public: // typedefs for STL compatibility typedef lChar8 value_type; ///< character type - typedef int size_type; ///< size type - typedef int difference_type; ///< difference type + typedef lUInt32 size_type; ///< size type + typedef lInt32 difference_type; ///< difference type typedef value_type * pointer; ///< pointer to char type typedef value_type & reference; ///< reference to char type typedef const value_type * const_pointer; ///< pointer to const char type @@ -341,21 +346,29 @@ class lString8 void alloc(size_type sz); void free(); inline void addref() const { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return; +#endif #ifdef USE_ATOMIC_REFCOUNT pchunk->refCount.fetch_add(1); #else ++pchunk->refCount; #endif } - inline void release() { + inline void release() { + if (!pchunk) return; #ifdef USE_ATOMIC_REFCOUNT if (pchunk->refCount.fetch_sub(1) <= 1) free(); #else if (--pchunk->refCount==0) free(); #endif + pchunk = nullptr; } inline int refCount() { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return 0; +#endif return pchunk->refCount; } explicit lString8(lstring_chunk_t * chunk) : pchunk(chunk) { addref(); } @@ -366,6 +379,11 @@ class lString8 explicit lString8( int size ) : pchunk(EMPTY_STR_8) { addref(); reserve(size); } /// copy constructor lString8(const lString8 & str) : pchunk(str.pchunk) { addref(); } + /// move constructor + lString8(lString8&& str) : pchunk(nullptr) { + pchunk = str.pchunk; + str.pchunk = nullptr; + } /// constructor from C string explicit lString8(const value_type * str); /// constructor from 16-bit C string @@ -388,6 +406,18 @@ class lString8 } return *this; } + /// move assignment + lString8 & assign(lString8 && str) + { + if (pchunk!=str.pchunk) + { + release(); + pchunk = str.pchunk; + str.pchunk = nullptr; + //addref(); + } + return *this; + } /// C-string assignment lString8 & assign(const value_type * str); /// C-string fragment assignment @@ -398,6 +428,8 @@ class lString8 lString8 & operator = (const value_type * str) { return assign(str); } /// string copy assignment lString8 & operator = (const lString8 & str) { return assign(str); } + /// string copy assignment + lString8 & operator = (lString8 && str) { return assign(std::move(str)); } /// erase part of string lString8 & erase(size_type offset, size_type count); /// append C-string @@ -522,7 +554,7 @@ class lString8 /// changes buffer size void resize(size_type count = 0, value_type e = 0); /// returns maximum number of chars that can fit into buffer - size_type capacity() const { return pchunk->size-1; } + size_type capacity() const { return pchunk->size; } /// reserve space for specified amount of chars void reserve(size_type count = 0); /// returns true if string is empty @@ -586,8 +618,8 @@ class lString16 public: // typedefs for STL compatibility typedef lChar16 value_type; - typedef int size_type; - typedef int difference_type; + typedef lUInt32 size_type; + typedef lInt32 difference_type; typedef value_type * pointer; typedef value_type & reference; typedef const value_type * const_pointer; @@ -601,21 +633,29 @@ class lString16 void alloc(size_type sz); void free(); inline void addref() const { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return; +#endif #ifdef USE_ATOMIC_REFCOUNT pchunk->refCount.fetch_add(1); #else ++pchunk->refCount; #endif } - inline void release() { + inline void release() { + if (!pchunk) return; #ifdef USE_ATOMIC_REFCOUNT if (pchunk->refCount.fetch_sub(1) <= 1) free(); #else if (--pchunk->refCount==0) free(); #endif + pchunk = nullptr; } inline int refCount() { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return 0; +#endif return pchunk->refCount; } public: @@ -624,6 +664,11 @@ class lString16 explicit lString16() : pchunk(EMPTY_STR_16) { addref(); } /// copy constructor lString16(const lString16 & str) : pchunk(str.pchunk) { addref(); } + /// move constructor + lString16(lString16 && str) : pchunk(nullptr) { + pchunk = str.pchunk; + str.pchunk = nullptr; + } /// constructor from wide c-string lString16(const value_type * str); /// constructor from 8bit c-string (ASCII only) @@ -648,6 +693,17 @@ class lString16 } return *this; } + /// move assignment from string + lString16 & assign(lString16 && str) + { + if (pchunk!=str.pchunk) + { + release(); + pchunk = str.pchunk; + str.pchunk = nullptr; + } + return *this; + } /// assignment from c-string lString16 & assign(const value_type * str); /// assignment from 8bit c-string (ASCII only) @@ -664,8 +720,12 @@ class lString16 lString16 & operator = (const lChar8 * str) { return assign(str); } /// assignment from string lString16 & operator = (const lString16 & str) { return assign(str); } + /// move assignment from string + lString16 & operator = (lString16 && str) { return assign(std::move(str)); } lString16 & erase(size_type offset, size_type count); + lString16 & append(const lChar32 * str); + lString16 & append(const lChar32 * str, size_type count); lString16 & append(const value_type * str); lString16 & append(const value_type * str, size_type count); lString16 & append(const lChar8 * str); @@ -753,7 +813,7 @@ class lString16 /// resizes string buffer, appends with specified character if buffer is being extended void resize(size_type count = 0, value_type e = 0); /// returns string buffer size - size_type capacity() const { return pchunk->size-1; } + size_type capacity() const { return pchunk->size; } /// ensures string buffer can hold at least count characters void reserve(size_type count = 0); /// erase all extra characters from end of string after size @@ -821,8 +881,8 @@ class lString32 public: // typedefs for STL compatibility typedef lChar32 value_type; - typedef int size_type; - typedef int difference_type; + typedef lUInt32 size_type; + typedef lInt32 difference_type; typedef value_type * pointer; typedef value_type & reference; typedef const value_type * const_pointer; @@ -836,21 +896,29 @@ class lString32 void alloc(size_type sz); void free(); inline void addref() const { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return; +#endif #ifdef USE_ATOMIC_REFCOUNT pchunk->refCount.fetch_add(1); #else ++pchunk->refCount; #endif } - inline void release() { + inline void release() { + if (!pchunk) return; #ifdef USE_ATOMIC_REFCOUNT if (pchunk->refCount.fetch_sub(1) <= 1) free(); #else if (--pchunk->refCount==0) free(); #endif + pchunk = nullptr; } inline int refCount() { +#if LDOM_USE_OWN_MEM_MAN==1 + if (ls_storage_is_destroyed()) return 0; +#endif return pchunk->refCount; } public: @@ -859,6 +927,8 @@ class lString32 explicit lString32() : pchunk(EMPTY_STR_32) { addref(); } /// copy constructor lString32(const lString32 & str) : pchunk(str.pchunk) { addref(); } + /// move constructor + lString32(lString32 && str) : pchunk(str.pchunk) { str.pchunk = nullptr; } /// constructor from wide c-string lString32(const value_type * str); /// constructor from 8bit c-string (ASCII only) @@ -870,7 +940,9 @@ class lString32 /// constructor from another string substring explicit lString32(const lString32 & str, size_type offset, size_type count); /// desctructor - ~lString32() { release(); } + ~lString32() { + release(); + } /// assignment from string lString32 & assign(const lString32 & str) @@ -883,6 +955,17 @@ class lString32 } return *this; } + /// assignment from string + lString32 & assign(lString32 && str) + { + if (pchunk!=str.pchunk) + { + release(); + pchunk = str.pchunk; + str.pchunk = nullptr; + } + return *this; + } /// assignment from c-string lString32 & assign(const value_type * str); /// assignment from 8bit c-string (ASCII only) @@ -899,12 +982,16 @@ class lString32 lString32 & operator = (const lChar8 * str) { return assign(str); } /// assignment from string lString32 & operator = (const lString32 & str) { return assign(str); } + /// moving assignment from string + lString32 & operator = (lString32 && str) { return assign(std::move(str)); } lString32 & erase(size_type offset, size_type count); lString32 & append(const value_type * str); lString32 & append(const value_type * str, size_type count); lString32 & append(const lChar8 * str); lString32 & append(const lChar8 * str, size_type count); + lString32 & append(const lChar16 * str); + lString32 & append(const lChar16 * str, size_type count); lString32 & append(const lString32 & str); lString32 & append(const lString32 & str, size_type offset, size_type count); lString32 & append(size_type count, value_type ch); @@ -1036,7 +1123,7 @@ class lString32 /// resizes string buffer, appends with specified character if buffer is being extended void resize(size_type count = 0, value_type e = 0); /// returns string buffer size - size_type capacity() const { return pchunk->size-1; } + size_type capacity() const { return pchunk->size; } /// ensures string buffer can hold at least count characters void reserve(size_type count = 0); /// erase all extra characters from end of string after size @@ -1186,6 +1273,7 @@ inline bool operator != (const lChar8 * s1, const lString16& s2 ) { return s2.compare(s1)!=0; } inline bool operator != (const lChar8 * s1, const lString32& s2 ) { return s2.compare(s1)!=0; } +inline lString32 operator + (const lString32 &s1, const lChar16 * s2) { lString32 s(s1); s.append(s2); return s; } inline lString32 operator + (const lString32 &s1, const lString32 &s2) { lString32 s(s1); s.append(s2); return s; } inline lString32 operator + (const lString32 &s1, const lChar32 * s2) { lString32 s(s1); s.append(s2); return s; } inline lString32 operator + (const lString32 &s1, const lChar8 * s2) { lString32 s(s1); s.append(s2); return s; } @@ -1215,6 +1303,11 @@ inline lString8 operator + (const lString8 &s1, fmt::decimal v) inline lString8 operator + (const lString8 &s1, fmt::hex v) { lString8 s(s1); s.appendHex(v.get()); return s; } +inline lString16 operator + (const lString16 &s1, const lChar32 * s2) { lString16 s(s1); s.append(s2); return s; } +inline lString16 operator + (const lString16 &s1, const lString16 &s2) { lString16 s(s1); s.append(s2); return s; } +inline lString16 operator + (const lString16 &s1, const lChar16 * s2) { lString16 s(s1); s.append(s2); return s; } +inline lString16 operator + (const lString16 &s1, fmt::decimal v) { lString16 s(s1); s.appendDecimal(v.get()); return s; } +inline lString16 operator + (const lString16 &s1, fmt::hex v) { lString16 s(s1); s.appendHex(v.get()); return s; } lString8 UnicodeToTranslit( const lString32 & str ); /// converts wide unicode string to local 8-bit encoding @@ -1275,6 +1368,9 @@ bool splitIntegerList( lString32 s, lString32 delim, int & value1, int & value2 #if LDOM_USE_OWN_MEM_MAN==1 void free_ls_storage(); +bool ls_storage_is_destroyed(); +/// checks free list in string chunk storage for consistency +void check_ls_storage(const char * msg); #endif #endif // __LV_STRING_H_INCLUDED__ diff --git a/crengine/src/lvfont/lvfreetypeface.cpp b/crengine/src/lvfont/lvfreetypeface.cpp index 53b4057822..77aaca9c7b 100644 --- a/crengine/src/lvfont/lvfreetypeface.cpp +++ b/crengine/src/lvfont/lvfreetypeface.cpp @@ -301,6 +301,9 @@ static LVFontGlyphCacheItem *newItem(LVFontLocalGlyphCache *local_cache, lChar32 if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) cr_correct_gamma_buf(item->bmp, bmp_sz, gammaIndex); break; + default: + // no correction + break; } } item->origin_x = (lInt16) slot->bitmap_left; @@ -343,6 +346,9 @@ static LVFontGlyphCacheItem *newItem(LVFontLocalGlyphCache *local_cache, lUInt32 if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) cr_correct_gamma_buf(item->bmp, bmp_sz, gammaIndex); break; + default: + // no correction + break; } } item->origin_x = (lInt16) slot->bitmap_left; @@ -2784,7 +2790,7 @@ int LVFreeTypeFace::DrawTextString(LVDrawBuf *buf, int x, int y, const lChar32 * if ( y + text_height < clip.top || y >= clip.bottom ) return 0; - unsigned int i; + int i; //lUInt16 prev_width = 0; lChar32 ch; // measure character widths diff --git a/crengine/src/lvmemman.cpp b/crengine/src/lvmemman.cpp index b035103e41..6ab81e5fb8 100644 --- a/crengine/src/lvmemman.cpp +++ b/crengine/src/lvmemman.cpp @@ -130,7 +130,9 @@ void cr_sigaction(int signal, siginfo_t *info, void *reserved) } #endif +#ifdef _LINUX static bool signals_are_set = false; +#endif void crSetSignalHandler() { #ifdef _LINUX @@ -168,8 +170,8 @@ lv_FatalErrorHandler_t * lvFatalErrorHandler = &lvDefFatalErrorHandler; void crFatalError( int code, const char * errorText ) { - if (file_to_remove_on_crash[0]) - LVDeleteFile(Utf8ToUnicode(lString8(file_to_remove_on_crash))); + if (file_to_remove_on_crash[0]) + LVDeleteFile(Utf8ToUnicode(lString8(file_to_remove_on_crash))); lvFatalErrorHandler( code, errorText ); } diff --git a/crengine/src/lvstream/ziphdr.h b/crengine/src/lvstream/ziphdr.h index 929da43789..40351ce34d 100644 --- a/crengine/src/lvstream/ziphdr.h +++ b/crengine/src/lvstream/ziphdr.h @@ -27,7 +27,7 @@ #pragma pack(push, 1) -typedef struct { +struct ZipLocalFileHdr { lUInt32 Mark; // 0 lUInt8 UnpVer; // 4 lUInt8 UnpOS; // 5 @@ -71,7 +71,7 @@ typedef struct { // Omitted fields (which follow this structure): // FileName (size = NameLen) // ExtraField (size = AddLen) -} ZipLocalFileHdr; +}; struct ZipHd2 { diff --git a/crengine/src/lvstring.cpp b/crengine/src/lvstring.cpp index cb8efb2a91..c484c23fcb 100644 --- a/crengine/src/lvstring.cpp +++ b/crengine/src/lvstring.cpp @@ -61,6 +61,7 @@ extern "C" { } #endif +// define LS_DEBUG_CHECK for debug string chunk allocations #define LS_DEBUG_CHECK // set to 1 to enable debugging @@ -185,44 +186,90 @@ struct lstring_chunk_slice_t { { p->buf8 = (char*)(p+1); p->size = 0; + p->len = 0; } - (pEnd-1)->buf8 = NULL; + (pEnd-1)->buf8 = nullptr; } ~lstring_chunk_slice_t() { free( pChunks ); + pChunks = nullptr; + pEnd = nullptr; + pFree = nullptr; + } + void check_free_node_chain(const char * msg = "free node chunk corrupted") { + lstring8_chunk_t * p = pFree; + while (p != nullptr) { + if (p < pChunks || p >= pEnd) { + printf("corrupted chunk %p\n", p); + crFatalError(3, msg); + } + p = (lstring8_chunk_t *)p->buf8; + } } inline lstring8_chunk_t * alloc_chunk() { lstring8_chunk_t * res = pFree; pFree = (lstring8_chunk_t *)res->buf8; +#ifdef LS_DEBUG_CHECK + if ((lstring8_chunk_t *)res < pChunks || (lstring8_chunk_t *)res >= pEnd) { + crFatalError(); // corrupted pointer + } + res->buf8 = nullptr; + res->size = 0; + res->len = 0; + res->refCount = 0; +#endif return res; } inline lstring16_chunk_t * alloc_chunk16() { lstring16_chunk_t * res = (lstring16_chunk_t *)pFree; pFree = (lstring8_chunk_t *)res->buf16; +#ifdef LS_DEBUG_CHECK + if ((lstring8_chunk_t *)res < pChunks || (lstring8_chunk_t *)res >= pEnd) { + crFatalError(); // corrupted pointer + } + res->buf16 = nullptr; + res->size = 0; + res->len = 0; + res->refCount = 0; +#endif return res; } inline lstring32_chunk_t * alloc_chunk32() { lstring32_chunk_t * res = (lstring32_chunk_t *)pFree; pFree = (lstring8_chunk_t *)res->buf32; +#ifdef LS_DEBUG_CHECK + if ((lstring8_chunk_t *)res < pChunks || (lstring8_chunk_t *)res >= pEnd) { + crFatalError(); // corrupted pointer + } + res->buf32 = nullptr; + res->size = 0; + res->len = 0; + res->refCount = 0; +#endif return res; } inline bool free_chunk( lstring8_chunk_t * pChunk ) { if (pChunk < pChunks || pChunk >= pEnd) return false; // chunk does not belong to this slice -/* + #ifdef LS_DEBUG_CHECK if (!pChunk->size) { crFatalError(); // already freed!!! } + if (pChunk->refCount) + { + crFatalError(); // has references + } pChunk->size = 0; + pChunk->len = 0; #endif -*/ + pChunk->buf8 = (char *)pFree; pFree = pChunk; return true; @@ -231,15 +278,19 @@ struct lstring_chunk_slice_t { { if ((lstring8_chunk_t *)pChunk < pChunks || (lstring8_chunk_t *)pChunk >= pEnd) return false; // chunk does not belong to this slice -/* + #ifdef LS_DEBUG_CHECK if (!pChunk->size) { crFatalError(); // already freed!!! } + if (pChunk->refCount) + { + crFatalError(); // has references + } pChunk->size = 0; #endif -*/ + pChunk->buf16 = (lChar16 *)pFree; pFree = (lstring8_chunk_t *)pChunk; return true; @@ -248,15 +299,19 @@ struct lstring_chunk_slice_t { { if ((lstring8_chunk_t *)pChunk < pChunks || (lstring8_chunk_t *)pChunk >= pEnd) return false; // chunk does not belong to this slice -/* + #ifdef LS_DEBUG_CHECK if (!pChunk->size) { crFatalError(); // already freed!!! } + if (pChunk->refCount) + { + crFatalError(); // has references + } pChunk->size = 0; #endif -*/ + pChunk->buf32 = (lChar32 *)pFree; pFree = (lstring8_chunk_t *)pChunk; return true; @@ -269,6 +324,7 @@ struct lstring_chunk_slice_t { static lstring_chunk_slice_t * slices[MAX_SLICE_COUNT]; static int slices_count = 0; static bool slices_initialized = false; +static bool slices_destroyed = false; #endif #if (LDOM_USE_OWN_MEM_MAN == 1) @@ -283,14 +339,26 @@ void free_ls_storage() { if (!slices_initialized) return; - for (int i=0; i=0; --i) { - delete slices[i]; + lstring_chunk_slice_t * slice = slices[i]; + slice->check_free_node_chain(msg); } - slices_count = 0; - slices_initialized = false; } + lstring8_chunk_t * lstring8_chunk_t::alloc() { if (!slices_initialized) @@ -568,7 +636,7 @@ inline void _lStr_memcpy(lChar32 * dst, const lChar32 * src, int count) inline void _lStr_memcpy(lChar8 * dst, const lChar8 * src, int count) { - memcpy(dst, (const lChar8 *) src, count); + memmove(dst, (const lChar8 *) src, count); } inline void _lStr_memset(lChar16 * dst, lChar16 value, int count) @@ -882,23 +950,28 @@ int lStr_cmp(const lChar16 * dst, const lChar32 * src) void lString32::free() { - if ( pchunk==EMPTY_STR_32 ) + if ( pchunk==EMPTY_STR_32 || pchunk == nullptr) return; //assert(pchunk->buf32[pchunk->len]==0); - ::free(pchunk->buf32); + if (pchunk->buf32 != nullptr) { + ::free(pchunk->buf32); + } #if (LDOM_USE_OWN_MEM_MAN == 1) - for (int i=slices_count-1; i>=0; --i) - { - if (slices[i]->free_chunk32(pchunk)) - return; + if (!ls_storage_is_destroyed()) { + for (int i=slices_count-1; i>=0; --i) + { + if (slices[i]->free_chunk32(pchunk)) + return; + } + crFatalError(); // wrong pointer!!! } - crFatalError(); // wrong pointer!!! #else ::free(pchunk); #endif + pchunk = nullptr; } -void lString32::alloc(int sz) +void lString32::alloc(size_type sz) { #if (LDOM_USE_OWN_MEM_MAN == 1) pchunk = lstring_chunk_t::alloc(); @@ -1190,8 +1263,13 @@ void lString32::reserve(size_type n) else { lstring_chunk_t * poldchunk = pchunk; + // ensure that reserved space is enough for len + size_type sz = n; + if (sz <= pchunk->len) { + sz = pchunk->len + 1; + } release(); - alloc( n ); + alloc( sz ); _lStr_memcpy( pchunk->buf32, poldchunk->buf32, poldchunk->len+1 ); pchunk->len = poldchunk->len; } @@ -1236,6 +1314,7 @@ void lString32::resize(size_type n, lChar32 e) // fill with data if expanded for (size_type i=pchunk->len; ibuf32[i] = e; + pchunk->len = n; pchunk->buf32[pchunk->len] = 0; } @@ -1273,6 +1352,18 @@ lString32 & lString32::append(const lChar8 * str, size_type count) return *this; } +lString32 & lString32::append(const lChar16 * str) +{ + size_type len = _lStr_len(str); + return append(Utf16ToUnicode(str, len)); +} + +lString32 & lString32::append(const lChar16 * str, size_type count) +{ + reserve(pchunk->len + count); + return append(Utf16ToUnicode(str, count)); +} + lString32 & lString32::append(const lString32 & str) { size_type len2 = pchunk->len + str.pchunk->len; @@ -1324,8 +1415,10 @@ lString32 & lString32::insert(size_type p0, const lString32 & str) p0 = pchunk->len; int count = str.length(); reserve( pchunk->len+count ); - for (size_type i=pchunk->len-1; i>=p0; i--) - pchunk->buf32[i+count] = pchunk->buf32[i]; + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf32[i] = pchunk->buf32[i - count]; + // for (size_type i=pchunk->len-1; i>=p0; i--) + // pchunk->buf32[i+count] = pchunk->buf32[i]; _lStr_memcpy(pchunk->buf32 + p0, str.c_str(), count); pchunk->len += count; pchunk->buf32[pchunk->len] = 0; @@ -1593,7 +1686,7 @@ bool lString32::atod( double &d, char dp ) const { s++; } } - if (res && *s == dp) { + if (res && (unsigned)*s == (unsigned)dp) { // decimal point found s++; res = false; @@ -1683,23 +1776,28 @@ const lString32 lString32::empty_str; void lString16::free() { - if ( pchunk==EMPTY_STR_16 ) + if ( pchunk==EMPTY_STR_16 || pchunk == nullptr) return; //assert(pchunk->buf16[pchunk->len]==0); - ::free(pchunk->buf16); + if (pchunk->buf16 != nullptr) { + ::free(pchunk->buf16); + } #if (LDOM_USE_OWN_MEM_MAN == 1) - for (int i=slices_count-1; i>=0; --i) - { - if (slices[i]->free_chunk16(pchunk)) - return; + if (!ls_storage_is_destroyed()) { + for (int i=slices_count-1; i>=0; --i) + { + if (slices[i]->free_chunk16(pchunk)) + return; + } + crFatalError(); // wrong pointer!!! } - crFatalError(); // wrong pointer!!! #else ::free(pchunk); #endif + pchunk = nullptr; } -void lString16::alloc(int sz) +void lString16::alloc(size_type sz) { #if (LDOM_USE_OWN_MEM_MAN == 1) pchunk = lstring_chunk_t::alloc(); @@ -1993,8 +2091,13 @@ void lString16::reserve(size_type n) else { lstring_chunk_t * poldchunk = pchunk; + // ensure that reserved space is enough for len + size_type sz = n; + if (sz <= pchunk->len) { + sz = pchunk->len + 1; + } release(); - alloc( n ); + alloc( sz ); _lStr_memcpy( pchunk->buf16, poldchunk->buf16, poldchunk->len+1 ); pchunk->len = poldchunk->len; } @@ -2039,6 +2142,7 @@ void lString16::resize(size_type n, value_type e) // fill with data if expanded for (size_type i=pchunk->len; ibuf16[i] = e; + pchunk->len = n; pchunk->buf16[pchunk->len] = 0; } @@ -2059,6 +2163,17 @@ lString16 & lString16::append(const value_type * str, size_type count) return *this; } +lString16 & lString16::append(const lChar32 * str) +{ + size_type len = _lStr_len(str); + return append(UnicodeToUtf16(str, len)); +} + +lString16 & lString16::append(const lChar32 * str, size_type count) +{ + return append(UnicodeToUtf16(str, count)); +} + lString16 & lString16::append(const lChar8 * str) { size_type len = _lStr_len(str); @@ -2114,8 +2229,10 @@ lString16 & lString16::insert(size_type p0, const value_type * str) p0 = pchunk->len; int count = lStr_len(str); reserve( pchunk->len+count ); - for (size_type i=pchunk->len+count; i>p0; i--) - pchunk->buf16[i] = pchunk->buf16[i-1]; + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf16[i] = pchunk->buf16[i - count]; + //for (size_type i=pchunk->len+count; i>p0; i--) + // pchunk->buf16[i] = pchunk->buf16[i-1]; _lStr_memcpy(pchunk->buf16 + p0, str, count); pchunk->len += count; pchunk->buf16[pchunk->len] = 0; @@ -2127,8 +2244,10 @@ lString16 & lString16::insert(size_type p0, const value_type * str, size_type co if (p0>pchunk->len) p0 = pchunk->len; reserve( pchunk->len+count ); - for (size_type i=pchunk->len+count; i>p0; i--) - pchunk->buf16[i] = pchunk->buf16[i-1]; + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf16[i] = pchunk->buf16[i - count]; + // for (size_type i=pchunk->len+count; i>p0; i--) + // pchunk->buf16[i] = pchunk->buf16[i-1]; _lStr_memcpy(pchunk->buf16 + p0, str, count); pchunk->len += count; pchunk->buf16[pchunk->len] = 0; @@ -2140,8 +2259,10 @@ lString16 & lString16::insert(size_type p0, size_type count, value_type ch) if (p0>pchunk->len) p0 = pchunk->len; reserve( pchunk->len+count ); - for (size_type i=pchunk->len+count; i>p0; i--) - pchunk->buf16[i] = pchunk->buf16[i-1]; + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf16[i] = pchunk->buf16[i - count]; + // for (size_type i=pchunk->len+count; i>p0; i--) + // pchunk->buf16[i] = pchunk->buf16[i-1]; _lStr_memset(pchunk->buf16+p0, ch, count); pchunk->len += count; pchunk->buf16[pchunk->len] = 0; @@ -2154,8 +2275,10 @@ lString16 & lString16::insert(size_type p0, const lString16 & str) p0 = pchunk->len; int count = str.length(); reserve( pchunk->len+count ); - for (size_type i=pchunk->len+count; i>p0; i--) - pchunk->buf16[i] = pchunk->buf16[i-1]; + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf16[i] = pchunk->buf16[i - count]; + // for (size_type i=pchunk->len+count; i>p0; i--) + // pchunk->buf16[i] = pchunk->buf16[i-1]; _lStr_memcpy(pchunk->buf16 + p0, str.c_str(), count); pchunk->len += count; pchunk->buf16[pchunk->len] = 0; @@ -2361,22 +2484,27 @@ const lString16 lString16::empty_str; void lString8::free() { - if ( pchunk==EMPTY_STR_8 ) + if ( pchunk==EMPTY_STR_8 || pchunk == nullptr) return; - ::free(pchunk->buf8); + if (pchunk->buf8 != nullptr) { + ::free(pchunk->buf8); + } #if (LDOM_USE_OWN_MEM_MAN == 1) - for (int i=slices_count-1; i>=0; --i) - { - if (slices[i]->free_chunk(pchunk)) - return; + if (!ls_storage_is_destroyed()) { + for (int i=slices_count-1; i>=0; --i) + { + if (slices[i]->free_chunk(pchunk)) + return; + } + crFatalError(); // wrong pointer!!! } - crFatalError(); // wrong pointer!!! #else ::free(pchunk); #endif + pchunk = nullptr; } -void lString8::alloc(int sz) +void lString8::alloc(size_type sz) { #if (LDOM_USE_OWN_MEM_MAN == 1) pchunk = lstring_chunk_t::alloc(); @@ -2596,8 +2724,13 @@ void lString8::reserve(size_type n) else { lstring_chunk_t * poldchunk = pchunk; + // ensure that reserved space is enough for len + size_type sz = n; + if (sz <= pchunk->len) { + sz = pchunk->len + 1; + } release(); - alloc( n ); + alloc( sz ); _lStr_memcpy( pchunk->buf8, poldchunk->buf8, poldchunk->len+1 ); pchunk->len = poldchunk->len; } @@ -2642,6 +2775,7 @@ void lString8::resize(size_type n, lChar8 e) // fill with data if expanded for (size_type i=pchunk->len; ibuf8[i] = e; + pchunk->len = n; pchunk->buf8[pchunk->len] = 0; } @@ -2821,11 +2955,18 @@ lString8 & lString8::append(size_type count, lChar8 ch) lString8 & lString8::insert(size_type p0, size_type count, lChar8 ch) { - if (p0>pchunk->len) + if (count == 0) { + return *this; + } + if (p0 > pchunk->len) p0 = pchunk->len; - reserve( pchunk->len+count ); - for (size_type i=pchunk->len-1; i>=p0; i--) - pchunk->buf8[i+count] = pchunk->buf8[i]; + reserve( pchunk->len + count ); + // mindst + // p0 + count + for (size_type i=pchunk->len + count - 1; i>= p0 + count ; i--) + pchunk->buf8[i] = pchunk->buf8[i - count]; + // for (size_type i=pchunk->len-1; i>=p0; i--) + // pchunk->buf8[i+count] = pchunk->buf8[i]; //_lStr_memset(pchunk->buf8+p0, ch, count); memset(pchunk->buf8+p0, ch, count); pchunk->len += count; @@ -3107,7 +3248,7 @@ int lString32::pos(const lChar8 * subStr) const { int flg = 1; for (int j=0; jbuf32[i+j] != subStr[j]) + if ((unsigned)pchunk->buf32[i+j] != (unsigned)subStr[j]) { flg = 0; break; @@ -3131,7 +3272,7 @@ int lString32::pos(const lChar8 * subStr, int start) const { int flg = 1; for (int j=0; jbuf32[i+j] != subStr[j]) + if ((unsigned)pchunk->buf32[i+j] != (unsigned)subStr[j]) { flg = 0; break; @@ -5687,7 +5828,7 @@ void lStr_findWordBounds( const lChar32 * str, int sz, int pos, int & start, int start = end = pos; return; } - hwEnd = hwStart+1; + //hwEnd = hwStart+1; // skipping while alpha for (; hwStart>0; hwStart--) { @@ -5971,7 +6112,7 @@ bool lString32::startsWith(const lChar8 * substring) const const lChar32 * s1 = c_str(); const lChar8 * s2 = substring; for ( int i=0; i= dom_version ) { + if ( domVersionRequested >= (lUInt32)dom_version ) { return false; // ignore the whole declaration } } @@ -4639,8 +4639,8 @@ bool LVStyleSheet::parse( const char * str, bool higher_importance, lString32 co } LVCssSelector * selector = NULL; LVCssSelector * prev_selector; - int err_count = 0; - int rule_count = 0; + //int err_count = 0; + //int rule_count = 0; lUInt32 domVersionRequested = _doc->getDOMVersionRequested(); for (;*str;) { @@ -4678,14 +4678,14 @@ bool LVStyleSheet::parse( const char * str, bool higher_importance, lString32 co if ( !decl->parse( str, domVersionRequested, higher_importance, _doc, codeBase ) ) { err = true; - err_count++; + //err_count++; } else { // set decl to selectors for (LVCssSelector * p = selector; p; p=p->getNext()) p->setDeclaration( decl ); - rule_count++; + //rule_count++; } break; } diff --git a/crengine/src/lvtinydom.cpp b/crengine/src/lvtinydom.cpp index 71a705b41e..ecd2c387f5 100644 --- a/crengine/src/lvtinydom.cpp +++ b/crengine/src/lvtinydom.cpp @@ -8120,7 +8120,7 @@ void ldomDocumentWriter::OnTagBody() } #if MATHML_SUPPORT==1 - if ( _currNode->_insideMathML ) { + if ( _currNode && _currNode->_insideMathML ) { // At this point, the style for this node has been applied. // Check if the element (or any of its parent) is display:none, // in which case we don't need to spend time handling MathML that @@ -12637,7 +12637,7 @@ bool ldomXPointerEx::isSentenceStart() ldomNode * node = getNode(); lString32 text = node->getText(); - int textLen = text.length(); + //int textLen = text.length(); int i = _data->getOffset(); lChar32 currCh = getChar(text, i); @@ -12690,7 +12690,7 @@ bool ldomXPointerEx::isSentenceEnd() ldomNode * node = getNode(); lString32 text = node->getText(); - int textLen = text.length(); + //int textLen = text.length(); int i = _data->getOffset(); lChar32 currCh = getChar(text, i); @@ -19617,7 +19617,7 @@ bool LVPageMap::deserialize( ldomDocument * doc, SerialBuf & buf ) if ( buf.error() ) return false; _page_info_valid = (bool)pageInfoValid; - for ( int i=0; ideserialize( doc, buf ) ) { delete item; diff --git a/crengine/src/props.cpp b/crengine/src/props.cpp index 0bbb4ac368..1fa4b7bab0 100644 --- a/crengine/src/props.cpp +++ b/crengine/src/props.cpp @@ -338,7 +338,7 @@ int CRPropAccessor::getIntDef( const char * propName, int defValue ) const void CRPropAccessor::setHex( const char * propName, lUInt32 value ) { char s[16]; - sprintf(s, "0x%08lX", value); + sprintf(s, "0x%08X", value); setString( propName, Utf8ToUnicode(lString8(s)) ); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..4829b6fec5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,49 @@ +SET (CR3TEST_SOURCES + src/testmain.cpp + src/stringtest.cpp + src/lvstring2.h + src/lvstring2.cpp +) + +IF(${CMAKE_BUILD_TYPE} STREQUAL Debug) + ADD_DEFINITIONS( -D_DEBUG=1 -DDEBUG=1 ) +ELSE() + ADD_DEFINITIONS( -DNDEBUG ) +ENDIF(${CMAKE_BUILD_TYPE} STREQUAL Debug) + +if(MAC) + ADD_DEFINITIONS(-DCR3_DATA_DIR="" + -DUSE_FONTCONFIG=0 + ${CARBON_CFLAGS} + ) +ELSEIF (UNIX) + ADD_DEFINITIONS(-DCR3_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/cr3/") +ELSE() + ADD_DEFINITIONS(-DCR3_DATA_DIR="") +ENDIF(MAC) + +ADD_EXECUTABLE(cr3test ${CR3TEST_SOURCES} + src/lvstring2test.h + src/lvstring2test.cpp) +SET (EXTRA_LIBS fontconfig ${STD_LIBS}) +TARGET_LINK_LIBRARIES(cr3test crengine tinydict ${EXTRA_LIBS}) + +IF (UNIX) + INSTALL( TARGETS cr3test RUNTIME DESTINATION bin ) + INSTALL( DIRECTORY ../cr3qt/data DESTINATION share/cr3 + FILES_MATCHING PATTERN "*.css" ) + INSTALL( DIRECTORY ../cr3gui/data/hyph DESTINATION share/cr3 + FILES_MATCHING PATTERN "*.pattern" ) + INSTALL( DIRECTORY ../cr3qt/data/skins DESTINATION share/cr3/skins ) + INSTALL( FILES ../cr3qt/src/desktop/cr3.desktop DESTINATION share/applications ) + INSTALL( FILES ../cr3qt/src/desktop/cr3.appdata.xml DESTINATION share/metainfo ) + INSTALL( FILES ../cr3qt/src/desktop/cr3.png DESTINATION share/pixmaps ) + INSTALL( FILES ../cr3qt/src/desktop/cr3.xpm DESTINATION share/pixmaps ) +ELSE() + INSTALL( TARGETS cr3test RUNTIME DESTINATION . ) + INSTALL( DIRECTORY ../cr3qt/data/ DESTINATION . + FILES_MATCHING PATTERN "*.css" ) + INSTALL( DIRECTORY ../cr3gui/data/hyph DESTINATION . + FILES_MATCHING PATTERN "*.pattern" ) + INSTALL( DIRECTORY ../cr3qt/data/skins DESTINATION . ) +ENDIF(UNIX) diff --git a/tests/src/lvstring2.cpp b/tests/src/lvstring2.cpp new file mode 100644 index 0000000000..6c5a8c5c72 --- /dev/null +++ b/tests/src/lvstring2.cpp @@ -0,0 +1,23 @@ +#include "lvstring2.h" +#include + +using namespace lv; + +namespace lv { + +lChar32 fake_null_buffer_32 = 0; + +#ifdef DEBUG_TRACK_LSTRING2_ALLOC +lStringStats ls_alloc_stats; +void lStringStats::dump(const char * msg) { + printf("stats[%s]:" + " \talloc=%d \tfree=%d \tactive=%d" + " \tcopyc=%d \tmovec=%d \tcopyass=%d \tmoveass=%d" + "\n", msg, + allocCount, freeCount, allocCount-freeCount, + copyConstr, moveConstr, copyAssign, moveAssign + ); +} +#endif + +} diff --git a/tests/src/lvstring2.h b/tests/src/lvstring2.h new file mode 100644 index 0000000000..a94b324c93 --- /dev/null +++ b/tests/src/lvstring2.h @@ -0,0 +1,3560 @@ +#ifndef LVSTRING2_H +#define LVSTRING2_H + +/** + * Copy-on-write reference counting strings. + * + * Optimized for low footprint and high performance. + * Thread-safe if instantiated with std::atomic_int as refcounter_type. + * + * Designed to be a drop-in replacement for old lString8/lString16/lString32 in coolreader core. + * + * Empty string is internally represented by `nullptr`. + * + * Length, capacity and refcount are located in signle block alongside with character data, occupying 12 bytes for 4-byte size_type and refcounter_type. + * Space for additional zero-termination character is always allocated in addition to capacity() value. + * + * To minimize refcount checks, use writable reference to modify string content. + * + * Interface is partially compatible with std::string. + */ + +#include "lvtypes.h" +#include +#include +#include +#include + +#define DEBUG_TRACK_LSTRING2_ALLOC + +/// helper functions from lvstring +void lStr_uppercase( lChar32 * str, int len ); +void lStr_uppercase( lChar16 * str, int len ); +void lStr_uppercase( lChar8 * str, int len ); +void lStr_lowercase( lChar32 * str, int len ); +void lStr_lowercase( lChar16 * str, int len ); +void lStr_lowercase( lChar8 * str, int len ); +lUInt16 lGetCharProps( lChar32 ch ); +bool isAlNum(lChar32 ch); + + +// returns 0..15 if c is hex digit, -1 otherwise +int hexDigit( int c ); +// converts 0..15 to 0..f +char toHexDigit( int c ); + +namespace lv { + +#ifdef DEBUG_TRACK_LSTRING2_ALLOC +struct lStringStats { + int allocCount {0}; + int freeCount {0}; + int copyConstr {0}; + int moveConstr {0}; + int copyAssign {0}; + int moveAssign {0}; + void dump(const char * msg = ""); +}; +extern lStringStats ls_alloc_stats; + +#define LS_COUNT_ALLOC ls_alloc_stats.allocCount++; +#define LS_COUNT_FREE ls_alloc_stats.freeCount++; +#define LS_COUNT_COPY_CONSTR ls_alloc_stats.copyConstr++; +#define LS_COUNT_MOVE_CONSTR ls_alloc_stats.moveConstr++; +#define LS_COUNT_COPY_ASSIGN ls_alloc_stats.copyAssign++; +#define LS_COUNT_MOVE_ASSIGN ls_alloc_stats.moveAssign++; + +#else + +#define LS_COUNT_ALLOC +#define LS_COUNT_FREE +#define LS_COUNT_COPY_CONSTR +#define LS_COUNT_MOVE_CONSTR +#define LS_COUNT_COPY_ASSIGN +#define LS_COUNT_MOVE_ASSIGN + +#endif + + +// lv::fmt copy of fmt +namespace fmt { + class decimal { + lInt64 value; + public: + explicit decimal(lInt64 v) : value(v) { } + lInt64 get() const { return value; } + }; + + class hex { + lUInt64 value; + public: + explicit hex(lInt64 v) : value(v) { } + lUInt64 get() const { return value; } + }; +} + + +// Helper functions + +/// arbitrary char type strlen, supports nullptr arg +template +inline size_type str_len(const char_type * s) { + if (s == nullptr) { + return 0; + } + size_type i = 0; + while (s[i] != 0) { + i++; + } + return i; +} + +/// arbitrary char type strcmp, supports empty strings +template +inline int str_cmp(const char_type * s1, size_type sz1, const char_type * s2, size_type sz2) { + // support case when one of strings or both are empty + if (sz1 == 0) { + return sz2 > 0 ? -1 : 0; + } else if (sz2 == 0) { + return 1; + } + for (size_type i = 0; ; i++) { + if (i >= sz1) { + // equal or s2 is bigger + return i < sz2 ? -1 : 0; + } else if (i >= sz2) { + // less + return 1; + } + if (s1[i] < s2[i]) { + return -1; + } + if (s1[i] > s2[i]) { + return 1; + } + // char [i] is matching, continue + } +} + +/// arbitrary char type strcmp, both args are non-empty +template +inline int str_cmp_nonempty(const char_type * s1, size_type sz1, const char_type * s2, size_type sz2) { + // we are sure that sz1>0 && sz2>0 + for (size_type i = 0; ; i++) { + if (i >= sz1) { + // end of s1 + // equal or s2 is bigger + return (i < sz2) ? -1 : 0; + } else if (i >= sz2) { + // s2 not yet ended + return 1; + } + if (s1[i] < s2[i]) { + return -1; + } + if (s1[i] > s2[i]) { + return 1; + } + // char [i] is matching, continue + } +} + +/// arbitrary char type strcmp, both args are non-empty, s2 is zero-terminated +template +inline int str_cmp_nonempty(const char_type * s1, size_type sz1, const char_type * s2) { + // we are sure that sz1>0 && sz2>0 + for (size_type i = 0; ; i++) { + if (i >= sz1) { + // end of s1 + // equal or s2 is bigger + return s2[i] ? -1 : 0; + } else if (!s2[i]) { + // end of s2 + // bigger + return 1; + } + if (s1[i] < s2[i]) { + return -1; + } + if (s1[i] > s2[i]) { + return 1; + } + // char [i] is matching, continue + } +} + +/// arbitrary char type strcmp, both args are non-empty, zero-terminated +template +inline int str_cmp_nonempty(const char_type * s1, const char_type * s2) { + // we are sure that sz1>0 && sz2>0 + for (size_type i = 0; ; i++) { + if (!s1[i]) { + // end of s1 + // equal or s2 is bigger + return s2[i] ? -1 : 0; + } else if (!s2[i]) { + // end of s2 + // bigger + return 1; + } + if (s1[i] < s2[i]) { + return -1; + } + if (s1[i] > s2[i]) { + return 1; + } + // char [i] is matching, continue + } +} + +// String data buffer with ref count + +// forward declaration of string readonly class +template +class string_ro; +// forward declaration of string writable ref class +template +class string_wr; +// forward declaration of string class +template +class string; + +template +struct lstring_chunk_t { + + template friend class string_ro; + template friend class string; + template friend class string_wr; + + friend void test_lstring2_chunks(); + /// chunk allocation alignment in bytes + static constexpr size_type alloc_align_bytes = 16; + /// minimum reserved characters count to have allocated size (alloc_align_bytes) bytes + static constexpr size_type min_size = static_cast((alloc_align_bytes - sizeof(lstring_chunk_t)) / sizeof(char_type)) - 1; + /// character count increment to keep block size aligned + static constexpr size_type size_align = static_cast(alloc_align_bytes / sizeof(char_type)); + +public: + + //const char_type * data() const { return &buf[0]; } + + /// align reserved size in characters of block to have byte alignment alloc_align_bytes, size does not include trailing 0 character + inline static size_type alignSize(size_type sz) noexcept { + if (sz <= min_size) + return min_size; + return min_size + (((sz - min_size) + size_align - 1) & (0 - size_align)); + } + + /// get reference counter value + size_type getRefCount() const noexcept { + if constexpr (std::is_same_v) { + // simple refcount + return refCount; + } else { + // atomic + return refCount.load(std::memory_order_relaxed); + } + } + + /// returns true if ref counter value is 1 + bool isOwn() const noexcept { + if constexpr (std::is_same_v) { + // simple refcount + return refCount == 1; + } else { + // atomic + return refCount.load(std::memory_order_relaxed) == 1; + } + } + + /// returns true if ref counter value is 1 + bool isShared() const noexcept { + if constexpr (std::is_same_v) { + // simple refcount + return refCount > 1; + } else { + // atomic + return refCount.load(std::memory_order_relaxed) > 1; + } + } + + /// 1. Hook for incrementing the counter (compatible with boost::intrusive_ptr) + friend void intrusive_ptr_add_ref(const lstring_chunk_t* p) noexcept { + if constexpr (std::is_same_v) { + // simple refcount + p->refCount++; + } else { + p->refCount.fetch_add(1, std::memory_order_relaxed); + } + } + + /// 1. Hook for incrementing the counter (compatible with boost::intrusive_ptr) + friend void intrusive_ptr_add_ref_checknull(const lstring_chunk_t* p) noexcept { + if (p) { + if constexpr (std::is_same_v) { + // simple refcount + p->refCount++; + } else { + p->refCount.fetch_add(1, std::memory_order_relaxed); + } + } + } + + void testAddRef() { + refCount.fetch_add(1, std::memory_order_relaxed); + } + + void testReleaseRef() { + refCount.fetch_sub(1, std::memory_order_acq_rel); + } + + /// 2. Hook for decrementing and deleting the object (compatible with boost::intrusive_ptr) + friend void intrusive_ptr_release(const lstring_chunk_t* p) noexcept { + if constexpr (std::is_same_v) { + if ((p->refCount--) == 1) { + lstring_chunk_t::free(p); + } + } else { + if (p->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { + lstring_chunk_t::free(p); + } + } + } + + /// chunk allocation function: create string buffer with reserved sz characters + zero termination char, ref counter 1 + static lstring_chunk_t * alloc(size_type sz) noexcept { + LS_COUNT_ALLOC + //lstring_chunk_t * res = static_cast( ::malloc(sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)) ); + //lstring_chunk_t * res = static_cast( ::malloc(sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)) ); + sz = alignSize(sz); + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + res->size = sz; + res->len = 0; + res->refCount = 1; + return res; + } + + /// chunk allocation function: create string buffer initialized with count chars from string s with reserved sz characters + zero termination char, ref counter 1 + static lstring_chunk_t * alloc(const char_type * s, size_type count, size_type sz = 0) noexcept { + LS_COUNT_ALLOC + if (sz < count) + sz = count; + sz = alignSize(sz); + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + std::memcpy(res->buf, s, count * sizeof(char_type)); + res->size = sz; + res->len = count; + res->refCount = 1; + return res; + } + + /// chunk allocation function: create duplicate of this string buffer with ref counter 1, does not release this buffer + lstring_chunk_t * duplicate() noexcept { + LS_COUNT_ALLOC + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (size + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + std::memcpy(res->buf, buf, len * sizeof(char_type)); + res->size = size; + res->len = len; + res->refCount = 1; + return res; + } + + /// chunk allocation function: create duplicate of this string buffer with capacity newSize and ref counter 1, does not release this buffer + lstring_chunk_t * duplicate(size_type newSize) noexcept { + LS_COUNT_ALLOC + size_type sz = newSize; + size_type count = len > sz ? sz : len; + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + std::memcpy(res->buf, buf, count * sizeof(char_type)); + res->size = sz; + res->len = count; + res->refCount = 1; + return res; + } + + /// chunk allocation function: create duplicate of this string buffer with minimum capacity to store the data and ref counter 1, and replace dest buffer with created copy + /// Don't call on for shared destination buffer. + /// if destination buffer exists: + /// * if destination buffer has enough capacity to store this string -- just copy data + /// * if destination buffer capacity is too low, free it (assuming dest buffer is not shared) and replace with a created duplicate. + void duplicate_to_owned(lstring_chunk_t* &dest) noexcept { + if (dest && dest->size >= len) { + // destination has enough space for this string: copy content to destination buffer + // assuming dest buffer is not shared!!! + if (len) + std::memcpy(dest->buf, buf, len * sizeof(char_type)); + dest->len = len; + } else { + LS_COUNT_ALLOC + // allocate minimum available size enough for storing this data + size_type sz = alignSize(len); + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + if (len) + std::memcpy(res->buf, buf, len * sizeof(char_type)); + res->size = sz; + res->len = len; + res->refCount = 1; + if (dest) { + // assuming dest buffer is not shared!!! just free instead of release ref + free(dest); + } + dest = res; + } + } + + /// chunk allocation function: create duplicate of this string buffer fragment to store the data and ref counter 1, and replace dest buffer with created copy + /// Don't call on for shared destination buffer. + /// if destination buffer exists: + /// * if destination buffer has enough capacity to store this string -- just copy data + /// * if destination buffer capacity is too low, free it (assuming dest buffer is not shared) and replace with a created duplicate. + void duplicate_to_owned(lstring_chunk_t* &dest, size_type start, size_type count) noexcept { + if (start >= len || !count) { + // empty string assignment + if (dest) { + // just reset length + dest->len = 0; + } + return; + } + if (start + count > len) { + count = len - start; + } + if (dest && dest->size >= count) { + // destination has enough space for this string: copy content to destination buffer + // assuming dest buffer is not shared!!! + std::memcpy(dest->buf, buf + start, count * sizeof(char_type)); + dest->len = count; + } else { + LS_COUNT_ALLOC + + // allocate minimum available size enough for storing this data + size_type sz = alignSize(count); + size_t allocBytes = (sizeof(lstring_chunk_t) + sizeof(char_type) * (sz + 1)); + lstring_chunk_t * res = static_cast( std::aligned_alloc(alloc_align_bytes, allocBytes) ); + // copy characters + std::memcpy(res->buf, buf + start, count * sizeof(char_type)); + res->size = sz; + res->len = count; + res->refCount = 1; + if (dest) { + // assuming dest buffer is not shared!!! just free instead of release ref + free(dest); + } + dest = res; + } + } + + /// only copy content of other buffer to this buffer + /// Don't call for shared this! + /// returns number of characters copied + size_type copy_from(const lstring_chunk_t * p) noexcept { + if (!p || !p->len) { + // only reset buffer length if attempting to copy from empty buffer + len = 0; + } else { + len = p->len; + if (len > size) + len = size; + std::memcpy(buf, p->buf, len * sizeof(char_type)); + } + return len; + } + + /// free chunk memory + static void free( const lstring_chunk_t * pChunk ) noexcept { + LS_COUNT_FREE + ::free(const_cast(pChunk)); + } + +private: + lstring_chunk_t() : size(1), len(0) + { + refCount = 1; + } + ~lstring_chunk_t() {}; + // layout for 4-byte size_type and refcounew + /// 0: size (max capacity for chars) less 1 + size_type size; // 0 for free chunk + /// 4: length (number of chars in array) + size_type len; // count of chars in string + /// 8: reference counter + mutable refcounter_type refCount; // reference counter + /// 12: string characters buffer of variable size + char_type buf[0]; // z-string +}; + +/// returns number of elements to convert unicode codepoint into char_type utf. +/// * for sizeof(char_type)==4 returns 1 +/// * for sizeof(char_type)==2 returns number of utf16 codepoints(1 to 2) +/// * for sizeof(char_type)==2 returns number of utf8 codepoints(1 to 4) +template +inline lInt32 utfCodePointSize(lChar32 c) noexcept { + if constexpr(sizeof(char_type) == 1) { + // calculate number of utf8 codepoints + return (c < 0x80) ? 1 + : (c < 0x800) ? 2 + : (c < 0x10000) ? 3 + : 4; + } else if constexpr(sizeof(char_type) == 2) { + // calculate number of utf16 codepoints + return (c >= 0x10000) ? 2 : 1; + } else { + // assuming this is 4-byte type representing unicode codepoint + return 1; + } +} + +//constexpr lChar32 UNICODE_END_OF_STREAM = 0xFFFFFFFF; + +/// read one unicode point from utf stream from position s and advances position of s +/// * supports 1-byte (utf8), 2-byte (utf16) and 4-byte (utf32) inputs +/// * returns unicode codepoint value if it's decoded successfully +/// * returns invalid_char value if invalid data found in input buffer (advances pointer anyway) +/// * overlong sequence (e.g. 7-bit code encoded as 3 bytes) is not considered as invalid character +/// * returns end_of_stream value if reading position reaches end of buffer (pend) +template +inline lChar32 utfReadCodePoint(const char_type* &s, const char_type* pend) noexcept { + if (s >= pend) { + return end_of_stream; + } + lChar32 c0 = (*s++); + if constexpr(sizeof(char_type) == 1) { + // calculate number of utf8 codepoints + if (c0 < 0x80) { + // single-byte utf8 character + return c0; + } + if ((c0 & 0xC0) == 0x80) { + // byte 10xx_xxxx is unexpected begin of utf8 sequence, return invalid char + return invalid_char; + } + if (s >= pend) { + // end of stream reached while waiting for additional bytes of codepoint -- indicate wrong character + // next call to this function will return end_of_stream because we reached end of stream + return invalid_char; + } + lChar32 c1 = (*s++); + if ((c1 & 0xC0) != 0x80) { + // byte 10xx_xxxx is expected for all additional bytes of utf8 sequence + return invalid_char; + } + if ((c0 & 0xE0) == 0xC0) { + // 2-byte codepoint + return ((c0 & 0x1F) << 6) | (c1 & 0x3f); + } + // need one more byte + if (s >= pend) { + // end of stream reached while waiting for additional bytes of codepoint -- indicate wrong character + // next call to this function will return end_of_stream because we reached end of stream + return invalid_char; + } + // read byte + lChar32 c2 = (*s++); + if ((c2 & 0xC0) != 0x80) { + // byte 10xx_xxxx is expected for all additional bytes of utf8 sequence + return invalid_char; + } + if ((c0 & 0xF0) == 0xE0) { + // 3-byte codepoint + c0 = ((c0 & 0x0F) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f); + // validate value to be in valid unicode range + return (c0 > 0x10FFFF || (c0 >= 0xD800 && c0 <= 0xDFFF)) ? invalid_char : c0; + } + // need one more byte + if (s >= pend) { + // end of stream reached while waiting for additional bytes of codepoint -- indicate wrong character + // next call to this function will return end_of_stream because we reached end of stream + return invalid_char; + } + // read byte + lChar32 c3 = (*s++); + if ((c3 & 0xC0) != 0x80) { + // byte 10xx_xxxx is expected for all additional bytes of utf8 sequence + return invalid_char; + } + if ((c0 & 0xF8) == 0xF0) { + // 3-byte codepoint + c0 = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f); + // validate value to be in valid unicode range + return (c0 > 0x10FFFF || (c0 >= 0xD800 && c0 <= 0xDFFF)) ? invalid_char : c0; + } + // incorrect utf8 sequence - 4-byte max length is expected + return invalid_char; + } else if constexpr(sizeof(char_type) == 2) { + // calculate number of utf16 codepoints + if (c0 >= 0xD800 && c0 <= 0xDFFF) { + // c0 is either low or high part of surrogate pair + if (c0 >= 0xDC00) { + // found low part of surrogate pair - invalid position inside utf16 stream -- skip it + return invalid_char; + } + // c0 is high part of surrogate pair, should be followed by low part in range (c0 >= 0xDC00 && c0 <= 0xDFFF) + if (s >= pend) { + // there must follow low part of surrogate pair, but end of stream reached + // return invalid_char -- next call to this function will return end_of_stream + return invalid_char; + } + // reading low part of surrogate pair + lChar32 c1 = (*s++); + if (c1 < 0xDC00 || c1 > 0xDFFF) { + // range is out of low part of surrogate pair + return invalid_char; + } + // c0 (high part of surrogate) must be in range 0xD800..0xDBFF + // c1 (low part of surrogate) must be in range 0xDC00..0xDFFF + return (((c0 & 0x3FF) << 10) | (c1 & 0x3FF)) + 0x10000; + } + // single-item character, not a surrogate pair return as is + return c0; + } else { + // assuming this is 4-byte type representing unicode codepoint + return (c0 > 0x10FFFF || (c0 >= 0xD800 && c0 <= 0xDFFF)) ? invalid_char: c0; + } +} + +/// Writes one unicode point to utf buffer `s` ending at `pend` and advances position of `s` +/// * supports 1-byte (utf8), 2-byte (utf16) and 4-byte (utf32) inputs +/// * returns unicode codepoint value if it's decoded successfully +/// * returns `ch' value if invalid data found in input buffer (advances pointer anyway) +/// * returns `invalid_char` value if codepoint passed on input is out of range (writing is skipped) +/// * returns `end_of_stream` value if buffer has not enough space +template +inline lChar32 utfWriteCodePoint(lChar32 ch, char_type* &s, const char_type* pend) noexcept { + // don't write invalid character to output + if constexpr(sizeof(char_type) == 1) { + // encode as utf8 + int space = static_cast( pend - s ); + if (ch < 0x80) { + // single byte utf8 sequence + if (space < 1) // no space in output buffer + return end_of_stream; + *s++ = static_cast(ch); + } else if (ch < 0x800) { + // 2-byte utf8 sequence + if (space < 2) + return end_of_stream; + s[0] = static_cast(0xC0 | (ch >> 6)); + s[1] = static_cast(0x80 | (ch & 0x3F)); + s += 2; + } else if (ch < 0x10000) { + // 3-byte utf8 sequence + if (ch >= 0xD800 && ch <= 0xDFFF) + return invalid_char; + if (space < 3) // no space in output buffer + return end_of_stream; + s[0] = static_cast(0xE0 | (ch >> 12)); + s[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + s[2] = static_cast(0x80 | ((ch) & 0x3F)); + s += 3; + } else { + // 4-byte utf8 sequence + if (ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF)) + return invalid_char; + if (space < 4) // no space in output buffer + return end_of_stream; + s[0] = static_cast(0xF0 | (ch >> 18)); + s[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + s[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + s[3] = static_cast(0x80 | ((ch) & 0x3F)); + s += 4; + } + } else if constexpr(sizeof(char_type) == 2) { + // encode as utf16 + int space = static_cast( pend - s ); + if (ch < 0x10000) { + // single word + if (ch >= 0xD800 && ch <= 0xDFFF) + return invalid_char; + if (space < 1) // no space in output buffer + return end_of_stream; + // write as is + *s++ = static_cast(ch); + } else { + // double word + if (ch > 0x10FFFF) + return invalid_char; + if (space < 2) // no space in output buffer + return end_of_stream; + // write 2 utf16 codes: high, then low + lChar32 c = ch - 0x10000; + s[0] = static_cast(0xD800 | ((c >> 10) & 0x3FF)); + s[1] = static_cast(0xDC00 | (c & 0x3FF)); + s += 2; + } + } else { + // encode as utf32 + if (ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF)) + return invalid_char; + if (s >= pend) { + return end_of_stream; + } + *s++ = ch; + } + return ch; +} + +/// Calculate buffer size in dst_char_type items to transcode utf string from src_char_type to dst_char_type. +/// Assuming that invalid unicode codepoints will be filtered out during conversion. +template +inline lUInt32 utfConvDestBufferSize(const src_char_type * pstart, const src_char_type * pend) { + lUInt32 count = 0; + for(;;) { + // read next unicode codepoint + lChar32 ch = utfReadCodePoint(pstart, pend); + if (ch == end_of_stream) { + return count; + } + if (ch != invalid_char) { + // ignore invalid unicode codepoints + count += utfCodePointSize(ch); + } + } +} + +/// Convert utf string from src_char_type to dst_char_type. +/// Invalid unicode codepoints will be filtered out during conversion (Returns number of invalid characters skipped in errorCount.) +/// Returns number of dst_char_type utf elements written to destination buffer. +template +inline lUInt32 utfConvert(const src_char_type * &src_start, const src_char_type * src_end, dst_char_type * &dst_start, const dst_char_type * dst_end, lUInt32 &errorCount) { + errorCount = 0; + const dst_char_type* keep_dst_start = dst_start; + for(;;) { + // read next unicode codepoint + lChar32 ch = utfReadCodePoint(src_start, src_end); + if (ch == end_of_stream) { + return dst_start - keep_dst_start; + } + if (ch != invalid_char) { + lChar32 res = utfWriteCodePoint(ch, dst_start, dst_end); + if (res == end_of_stream) { + // no space left in dest buffer + return dst_start - keep_dst_start; + } + } else { + errorCount++; + } + } +} + +/// counts characters in utf-encoded buffer +/// * supports 1-byte (utf8), 2-byte (utf16) and 4-byte (utf32) inputs +template +inline lUInt32 utfCodePointCount(const char_type* pstart, const char_type* pend) noexcept { + lUInt32 count = 0; + for(;;) { + lChar32 ch = utfReadCodePoint(pstart, pend); + if (ch == end_of_stream) { + return count; + } + count++; + } + return count; +} + +/// strcmp for two strings of different utf encoding, both args are non-empty, zero-terminated +template +inline int str_cmp_utf_nonempty(const char_type1 * s1, size_type len1, const char_type2 * s2, size_type len2) { + // we are sure that sz1>0 && sz2>0 + const char_type1 * s1_end = s1 + len1; + const char_type2 * s2_end = s2 + len2; + for (;;) { + lChar32 ch1 = utfReadCodePoint(s1, s1_end); + lChar32 ch2 = utfReadCodePoint(s2, s2_end); + if (ch1 == end_of_stream) { + return (ch2 == end_of_stream) ? 0 : -1; + } + if (ch2 == end_of_stream) { + return 1; + } + if (ch1 < ch2) + return -1; + if (ch1 > ch2) + return 1; + } +} + + + +template +struct CodepointReadIterator { + struct EndOfStream {}; + CodepointReadIterator(const char_type * start, const char_type * end) noexcept : pos(start), pend(end) { } + CodepointReadIterator(const CodepointReadIterator& v) = default; + ~CodepointReadIterator() = default; + + lChar32 operator*() const noexcept { + const char_type * tmp = pos; + return utfReadCodePoint(tmp, pend); + } + CodepointReadIterator& operator++() { + utfReadCodePoint(pos, pend); + return *this; + } + bool operator != (EndOfStream) const { + return pos < pend; + } + private: + const char_type * pos; + const char_type * pend; +}; + +template +struct UtfDecodeRange { + + using iterator_t = CodepointReadIterator; + using sentinel_t = typename iterator_t::EndOfStream; + + UtfDecodeRange(const char_type * start, const char_type * end) noexcept : pstart(start), pend(end) { } + UtfDecodeRange(const UtfDecodeRange& v) = default; + ~UtfDecodeRange() noexcept = default; + + iterator_t begin() const { return iterator_t(pstart, pend); } + sentinel_t end() const { return sentinel_t(); } // Returns a completely different type! + + private: + const char_type * pstart; + const char_type * pend; +}; + + +extern lChar32 fake_null_buffer_32; + + +/// writable copy of string (guaranteed to be empty or have reference counter == 1 +/// can only be passed by reference created from string +/// allows modification without reference counting checks +template +class string_ro { + // friend class string_wr; + // friend class string; + template friend class string_ro; + template friend class string; + template friend class string_wr; + public: + // typedefs for STL compatibility + typedef char_type value_type; ///< character type + typedef lUInt32 size_type; ///< size type + typedef lInt32 difference_type; ///< difference type + typedef value_type * pointer; ///< pointer to char type + typedef value_type & reference; ///< reference to char type + typedef const value_type * const_pointer; ///< pointer to const char type + typedef const value_type & const_reference; ///< reference to const char type + //typedef string string_type; ///< normal shared string reference type + + // constant for not found / unspecified position + static const size_type npos = -1; + static lChar32 constexpr invalid_unicode = 0xfffffffe; + static lChar32 constexpr end_of_stream = 0xffffffff; + + typedef UtfDecodeRange utf_decode_range_t; + typedef lstring_chunk_t chunk_t; + + protected: + + static const size_type STRING_HASH_MULT = 31; + + /// disabled: always created as a synonim of the string with the same template parameters + string_ro() = default; + /// don't free pchunk - it's owned by string + ~string_ro() = default; + public: + + // ============================================================================ + // size and capacity + // ============================================================================ + + /// returns true if string is empty + bool empty() const noexcept { return pchunk == nullptr || pchunk->len==0; } + /// returns character count + size_type length() const noexcept { return pchunk == nullptr ? 0 : pchunk->len; } + /// returns character count (same as length) + size_type size() const noexcept { return pchunk == nullptr ? 0 : pchunk->len; } + /// returns maximum number of chars that can fit into buffer (there is always additional one char space for trailing 0 which is not counted) + size_type capacity() const noexcept { return pchunk==nullptr ? 0 : pchunk->size; } + + // ============================================================================ + // item index methods + // ============================================================================ + + /// index value. No index nor empty string checks. Will crash on empty string. + char_type operator [] (size_type index) const noexcept { + return pchunk->buf[index]; + } + + /// index ref. No index nor refcount checks. Will crash on empty string. Only use on non-empy string which owns data (refcount==1). + char_type& operator [] (size_type index) noexcept { + return pchunk->buf[index]; + } + + /// index value with bounds checking. No index nor empty string checks. Will crash on empty string. + /// Does not throw exception on index out of bounds, returns 0 instead. + char_type at(size_type index) const noexcept { + if (pchunk == nullptr || index >= pchunk->len) { + return 0; + } + return pchunk->buf[index]; + } + + /// index ref. No index nor refcount checks. Will crash on empty string. Only use on non-empy string which owns data (refcount==1). + /// Does not throw exception on index out of bounds, returns reference pointing to static 0 value instead. + char_type& at(size_type index) noexcept { + if (pchunk == nullptr || index >= pchunk->len) { + return *(static_cast(&fake_null_buffer_32)); + } + return pchunk->buf[index]; + } + + /// return C-style null-terminated string pointer + const char_type * c_str() const noexcept { + if (pchunk == nullptr) { + return reinterpret_cast(&fake_null_buffer_32); + } else { + // enforce null-termination + const_cast(pchunk)->buf[pchunk->len] = 0; + return pchunk->buf; + } + } + + /// returns last character from string, or 0 for empty string + char_type lastChar() const noexcept { + if (pchunk && pchunk->len) { + return pchunk->buf[pchunk->len-1]; + } else { + return 0; + } + } + + /// returns first character from string, or 0 for empty string + char_type firstChar() const noexcept { + if (pchunk && pchunk->len) { + return pchunk->buf[0]; + } else { + return 0; + } + } + + /// forward iterator - support for range by char_type + const char_type * begin() const noexcept { + if (pchunk) { + return pchunk->buf; + } else { + return nullptr; + } + } + + /// forward iterator - support for range by char_type + const char_type * end() const noexcept { + if (pchunk) { + return pchunk->buf + pchunk->len; + } else { + return nullptr; + } + } + + /// returns range object to read content as unicode codepoints -- use to iterate by codepoints + /// * for sizeof(char_type) == 1 the content is interpreted as utf8 sequence + /// * for sizeof(char_type) == 2 the content is interpreted as utf16 sequence + /// * for sizeof(char_type) == 4 the content is interpreted as utf32 sequence + /// if decoded codepoint value is not in valid unicode range, invalid_unicode value is returned + utf_decode_range_t unicodeRange() const noexcept { + if (pchunk && pchunk->len) { + return utf_decode_range_t(pchunk->buf, pchunk->buf + pchunk->len); + } else { + return utf_decode_range_t(nullptr, nullptr); + } + } + + /// returns subrange object to read content as unicode codepoints -- use to iterate by codepoints + /// Decoding starts from item [start] and ends at item [start + len - 1] + /// * for sizeof(char_type) == 1 the content is interpreted as utf8 sequence + /// * for sizeof(char_type) == 2 the content is interpreted as utf16 sequence + /// * for sizeof(char_type) == 4 the content is interpreted as utf32 sequence + /// if decoded codepoint value is not in valid unicode range, invalid_unicode value is returned + utf_decode_range_t unicodeRange(size_type start, size_type len) const noexcept { + if (pchunk && start < pchunk->len && len) { + if (start + len > pchunk->len) { + len = pchunk->len - start; + } + return utf_decode_range_t(pchunk->buf + start, pchunk->buf + start + len); + } else { + return utf_decode_range_t(nullptr, nullptr); + } + } + + /// assuming this string as utf-encoded (utf8/utf16/utf32), returns number of unicode codepoints inside the string. + size_type codePointCount() const noexcept { + if (pchunk && pchunk->len) { + return utfCodePointCount(pchunk->buf, pchunk->buf + pchunk->len); + } + return 0; + } + + /// for substring assuming this string as utf-encoded (utf8/utf16/utf32), returns number of unicode codepoints inside the string. + size_type codePointCount(size_type start, size_type len) const noexcept { + if (pchunk && start < pchunk->len && len) { + if (start + len > pchunk->len) { + len = pchunk->len - start; + } + return utfCodePointCount(pchunk->buf + start, pchunk->buf + start + len); + } + return 0; + } + + + /// calculate hash code for string (empty string has hash==0) + size_type getHash() const noexcept { + lUInt32 res = 0; + if (pchunk) { + for (size_type i=0; i < pchunk->len; i++) + res = res * STRING_HASH_MULT + pchunk->buf[i]; + } + return res; + } + + // ============================================================================ + // compare methods + // ============================================================================ + + /// compare this string with another string, returns -1 if this < s, 1 if this > s, 0 if equal + int compare(const string_ro& s) const noexcept { + if (pchunk == s.pchunk) { + // same string + return 0; + } + size_type sz1 = length(); + size_type sz2 = s.length(); + if (sz1 == 0) { + return sz2 > 0 ? -1 : 0; + } else if (sz2 == 0) { + return 1; + } + return str_cmp_nonempty(pchunk->buf, sz1, s.pchunk->buf, sz2); + } + + /// compare this string with another string (optionally of different type), returns -1 if this < s, 1 if this > s, 0 if equal + /// if string s has the same type as current string, use simple compare() + /// if string s has different type, both this string and `s` are read as utf unicode characters flow and compared by unicode codepoint values + template + int compareUtf(const string_ro& s) const noexcept { + if constexpr (sizeof(char_type) == sizeof(utf_type)) { + // for strings of the same character type, use simple compare() + return compare(s); + } else { + if (static_cast(pchunk) == static_cast(s.pchunk)) { + // same string + return 0; + } + // for strings of different character type (e.g. utf8<->utf32) compare unicode codepoints + size_type sz1 = length(); + size_type sz2 = s.length(); + if (sz1 == 0) { + return sz2 > 0 ? -1 : 0; + } else if (sz2 == 0) { + return 1; + } + return str_cmp_utf_nonempty(pchunk->buf, sz1, s.pchunk->buf, sz2); + } + } + + /// compare substring (pos..pos+n) of this string with another string, returns -1 if this < s, 1 if this > s, 0 if equal + int compare(size_type pos, size_type n, const string_ro& s) const noexcept { + if (!pchunk || pos >= pchunk->len) { + // this string fragment is empty + return s.empty() ? 0 : -1; + } + // clamp this fragment size + if (pos + n > pchunk->len) { + n = pchunk->len - pos; + } + // n > 0 + size_type sz2 = s.length(); + if (sz2 == 0) { + return 1; + } + return str_cmp_nonempty(pchunk->buf + pos, n, s.pchunk->buf, sz2); + } + + /// compare substring (pos..pos+n) of this string with substring of another string (pos2..pos2+n2), returns -1 if this < s, 1 if this > s, 0 if equal + int compare(size_type pos, size_type n, const string_ro& s, size_type pos2, size_type n2) const noexcept { + // check if another string fragment is empty + bool s_empty = (!s.pchunk || pos2 >= s.pchunk->len); + if (!pchunk || pos >= pchunk->len) { + // this string fragment is empty + return s_empty ? 0 : -1; + } + if (s_empty) { + // this string non-empty, another string is empty + return 1; + } + // both string fragments are non-empty + // clamp this fragment size + if (pos + n > pchunk->len) { + n = pchunk->len - pos; + } + // clamp other fragment size + if (pos2 + n2 > s.pchunk->len) { + n2 = s.pchunk->len - pos2; + } + // n > 0, n2 > 0 + return str_cmp_nonempty(pchunk->buf + pos, n, s.pchunk->buf + pos2, n2); + } + + /// compare substring (pos..pos+n) of this string with substring of another string s of len n2, returns -1 if this < s, 1 if this > s, 0 if equal + int compare(size_type pos, size_type n, const char_type * s, size_type n2) const noexcept { + // check if another string fragment is empty + bool s_empty = !s || !n2; + if (!pchunk || pos >= pchunk->len) { + // this string fragment is empty + return s_empty ? 0 : -1; + } + if (s_empty) { + // this string non-empty, another string is empty + return 1; + } + // both string fragments are non-empty + // clamp this fragment size + if (pos + n > pchunk->len) { + n = pchunk->len - pos; + } + // n > 0, n2 > 0 + return str_cmp_nonempty(pchunk->buf + pos, n, s, n2); + } + + /// compare substring (pos..pos+n) of this string with another string s, returns -1 if this < s, 1 if this > s, 0 if equal + int compare(size_type pos, size_type n, const char_type * s) const noexcept { + size_type n2 = (!s) ? 0 : str_len(s); + return compare(pos, n, s, n2); + } + + /// compare this string with string literal, returns -1 if this < s, 1 if this > s, 0 if equal + int compare(const char_type* s) const noexcept { + size_t sz1 = length(); + if (sz1 == 0) { + return (s != nullptr && *s != 0) ? -1 : 0; + } else if (s == nullptr || *s == 0) { + return 1; + } + return str_cmp_nonempty(pchunk->buf, sz1, s); + } + + /// returns true when strings are equal + bool operator == (const string_ro& s) const noexcept { + return compare(s) == 0; + } + + /// returns true when strings are not equal + bool operator != (const string_ro& s) const noexcept { + return compare(s) != 0; + } + + /// returns true when this string < s + bool operator < (const string_ro& s) const noexcept { + return compare(s) < 0; + } + + /// returns true when this string >= s + bool operator <= (const string_ro& s) const noexcept { + return compare(s) <= 0; + } + + /// returns true when this string > s + bool operator > (const string_ro& s) const noexcept { + return compare(s) > 0; + } + + /// returns true when this string <= s + bool operator >= (const string_ro& s) const noexcept { + return compare(s) >= 0; + } + + /// returns true when strings are equal + bool operator == (const char_type * s) const noexcept { + return compare(s) == 0; + } + + /// returns true when strings are not equal + bool operator != (const char_type * s) const noexcept { + return compare(s) != 0; + } + + /// returns true when this string < s + bool operator < (const char_type * s) const noexcept { + return compare(s) < 0; + } + + /// returns true when this string <= s + bool operator <= (const char_type * s) const noexcept { + return compare(s) <= 0; + } + + /// returns true when this string > s + bool operator > (const char_type * s) const noexcept { + return compare(s) > 0; + } + + /// returns true when this string >= s + bool operator >= (const char_type * s) const noexcept { + return compare(s) >= 0; + } + + + // ============================================================================ + // search methods + // ============================================================================ + + /// return first position of char c inside string starting from positon pos, or npos if no char is found. + size_type pos(char_type c, size_type start = 0) const noexcept { + if (pchunk) { + const char_type * buf = pchunk->buf; + size_type len = pchunk->len; + for (size_type i = start; i < len; i++) { + if (buf[i] == c) { + return i; + } + } + } + return npos; + } + + /// return first position of string s starting from positon pos, or npos if no char is found. + size_type pos(const string_ro& s, size_type start = 0) const noexcept { + if (pchunk && s.pchunk) { + size_type len = pchunk->len; + if (start >= len) { + return npos; + } + len -= start; + size_type slen = s.pchunk->len; + if (slen > len) { + return npos; + } + const char_type * sbuf = s.pchunk->buf; + const char_type * buf = pchunk->buf + start; + size_type maxstart = len - slen; + for (size_type i = 0; i <= maxstart; i++) { + size_type found = i + start; + for (size_type j = 0; j < slen; j++) { + if (buf[i + j] != sbuf[j]) { + found = npos; + break; + } + } + if (found != npos) { + return found; + } + } + } + return npos; + } + + /// return first position of null-term string starting from positon pos, or npos if no char is found. + size_type pos(const char_type* s, size_type start = 0) const noexcept { + if (pchunk && s && s[0]) { + size_type len = pchunk->len; + if (start >= len) { + return npos; + } + len -= start; + size_type slen = str_len(s); + if (slen > len) { + return npos; + } + const char_type * buf = pchunk->buf + start; + size_type maxstart = len - slen; + for (size_type i = 0; i <= maxstart; i++) { + size_type found = i + start; + for (size_type j = 0; j < slen; j++) { + if (buf[i + j] != s[j]) { + found = npos; + break; + } + } + if (found != npos) { + return found; + } + } + } + return npos; + } + + /// return last position of null-term string s, or npos if no char is found. + size_type rpos(const char_type* s) const noexcept { + if (pchunk && s && s[0]) { + size_type len = pchunk->len; + size_type slen = str_len(s); + if (slen > len) { + return npos; + } + const char_type * buf = pchunk->buf; + size_type maxstart = len - slen; + for (size_type i = maxstart; ; i--) { + size_type found = i; + for (size_type j = 0; j < slen; j++) { + if (buf[i + j] != s[j]) { + found = npos; + break; + } + } + if (found != npos) { + return found; + } + if (i == 0) { + break; + } + } + } + return npos; + } + + /// returns true if this string starts with s[0..count-1], pass count=npos to calculate string size internally + bool startsWith(const char_type* s, size_type count = npos) const noexcept { + if (pchunk && s && s[0]) { + size_type len = pchunk->len; + size_type slen = (count == npos) ? str_len(s) : count; + if (slen > len) { + return false; + } + const char_type * buf = pchunk->buf; + for (size_type j = 0; j < slen; j++) { + if (buf[j] != s[j]) { + return false; + } + } + return true; + } + return false; + } + + // ============================================================================ + // startsWith / endsWith + // ============================================================================ + + /// returns true if this string ends with s[0..count-1], pass count=npos to calculate string size internally + bool endsWith(const char_type* s, size_type count = npos) const noexcept { + if (pchunk && s && s[0]) { + size_type len = pchunk->len; + size_type slen = (count == npos) ? str_len(s) : count; + if (slen > len) { + return false; + } + const char_type * buf = pchunk->buf + (len - slen); + for (size_type j = 0; j < slen; j++) { + if (buf[j] != s[j]) { + return false; + } + } + return true; + } + return false; + } + + /// returns true if this string starts with s + bool startsWith(const string_ro& s) const noexcept { + if (pchunk && s.pchunk) { + size_type len = pchunk->len; + size_type slen = s.pchunk->len; + if (slen > len) { + return false; + } + const char_type * buf = pchunk->buf; + const char_type * sbuf = s.pchunk->buf; + for (size_type j = 0; j < slen; j++) { + if (buf[j] != sbuf[j]) { + return false; + } + } + return true; + } + return false; + } + + /// returns true if this string starts with s + bool endsWith(const string_ro& s) const noexcept { + if (pchunk && s.pchunk) { + size_type len = pchunk->len; + size_type slen = s.pchunk->len; + if (slen > len) { + return false; + } + const char_type * buf = pchunk->buf + (len - slen); + const char_type * sbuf = s.pchunk->buf; + for (size_type j = 0; j < slen; j++) { + if (buf[j] != sbuf[j]) { + return false; + } + } + return true; + } + return false; + } + + /// convert to integer + int atoi() const noexcept { + int sgn = 1; + int n = 0; + const char_type * s = c_str(); + // skip whitespace + while (*s == ' ' || *s == '\t') { + s++; + } + if (*s == '-') { + sgn = -1; + s++; + } else if (*s == '+') { + s++; + } + while (*s>='0' && *s<='9') { + n = n * 10 + ( (*s)-'0' ); + s++; + } + return (sgn>0)?n:-n; + } + + /// convert to integer + bool atoi(int& n) const noexcept { + n = 0; + int sgn = 1; + const char_type * s = c_str(); + // allow leading whitespace -- not an error + while (*s == ' ' || *s == '\t') + s++; + // support hex with prefix 0x + if ( s[0]=='0' && s[1]=='x') { + s+=2; + for (;*s;) { + int d = hexDigit(*s++); + if ( d>=0 ) + n = (n<<4) | d; + } + return true; + } + if (*s == '-') { + sgn = -1; + s++; + } + else if (*s == '+') { + s++; + } + if ( !(*s>='0' && *s<='9') ) + return false; + while (*s>='0' && *s<='9') { + if (n > 0x7fffffff/10) { + return false; + } + n = n * 10 + ( (*s++)-'0' ); + } + if ( sgn<0 ) + n = -n; + return *s=='\0' || *s==' ' || *s=='\t'; + } + + /// convert string to 64-bit integer, supports +/- for decimal, and 0x prefix for hex, skips leading whitespace + bool atoi(lInt64& n) const noexcept { + n = 0; + int sgn = 1; + const char_type * s = c_str(); + // allow leading whitespace -- not an error + while (*s == ' ' || *s == '\t') + s++; + // support hex with prefix 0x + if ( s[0]=='0' && s[1]=='x') { + s+=2; + for (;*s;) { + int d = hexDigit(*s++); + if ( d>=0 ) + n = (n<<4) | d; + } + return true; + } + if (*s == '-') { + sgn = -1; + s++; + } + else if (*s == '+') { + s++; + } + if ( !(*s>='0' && *s<='9') ) + return false; + while (*s>='0' && *s<='9') { + if (n > 0x7fffffffffffffffll/10) { + return false; + } + n = n * 10 + ( (*s++)-'0' ); + } + if ( sgn<0 ) + n = -n; + return *s=='\0' || *s==' ' || *s=='\t'; + } + + /// convert to 64 bit integer + lInt64 atoi64() const noexcept { + int sgn = 1; + lInt64 n = 0; + const lChar8 * s = c_str(); + while (*s == ' ' || *s == '\t') + s++; + if (*s == '-') + { + sgn = -1; + s++; + } + else if (*s == '+') + { + s++; + } + while (*s>='0' && *s<='9') + { + n = n * 10 + ( (*s)-'0' ); + s++; + } + return (sgn>0) ? n : -n; + } + + protected: + /// member variables must follow in the same order as in string for reinterpret cast + chunk_t * pchunk {nullptr}; +}; + +/// writable copy of string (guaranteed to be empty or have reference counter == 1 +/// can only be passed by reference created from string +/// allows modification without reference counting checks +template +class string_wr : public string_ro { +public: + // typedefs for STL compatibility + typedef char_type value_type; ///< character type + typedef lUInt32 size_type; ///< size type + typedef lInt32 difference_type; ///< difference type + typedef value_type * pointer; ///< pointer to char type + typedef value_type & reference; ///< reference to char type + typedef const value_type * const_pointer; ///< pointer to const char type + typedef const value_type & const_reference; ///< reference to const char type + typedef string_ro ro_string_type; ///< readonly string type + typedef string string_type; ///< normal shared string type + + // constant for not found / unspecified position + static const size_type npos = -1; + +private: + using typename string_ro::chunk_t; + using string_ro::pchunk; +public: + using string_ro::empty; + using string_ro::length; + using string_ro::size; + using string_ro::capacity; + + using string_ro::operator []; + using string_ro::at; + using string_ro::c_str; + + /// disabled: always created as a synonim of the string with the same template parameters + string_wr() {} + /// don't free pchunk - it's owned by string + ~string_wr() {} +public: + + /// return C-style null-terminated string pointer + const char_type * data() const noexcept { + return c_str(); + } + + /// return modifable C-style null-terminated string pointer; for empty string returns pointer to fake empty z-string buffer + char_type * data() noexcept { + if (pchunk == nullptr) { + return reinterpret_cast(&fake_null_buffer_32); + } else { + // enforce null-termination + const_cast(pchunk)->buf[pchunk->len] = 0; + return pchunk->buf; + } + } + + /// sets value to null string, freeing buffer + string_wr& clear() noexcept { + if (pchunk != nullptr) { + // use free instead of release because we own this string and ref count should be 1 anyway + chunk_t::free(pchunk); + pchunk = nullptr; + } + return *this; + } + + /// resets length to zero, preparing to modification with reserved size + string_wr& reset(size_type size) noexcept { + if (pchunk == nullptr) { + // buffer is allocated and has capacity at least size + pchunk = chunk_t::alloc(size); + } else { + // this string is non-empty + if (pchunk->size >= size) { + // this is already our own string with enough capacity -- just reset length + pchunk->len = 0; + } else { + // this string has not enough capacity for size, clear and alloc new + chunk_t::free(pchunk); + pchunk = chunk_t::alloc(size); + } + } + return *this; + } + + /// ensure that buffer has at least size capacity + string_wr& reserve(size_type size) noexcept { + if (size == 0) { + // reserve(0) is a non-binding request, just treat this empty string as owned + return *this; + } + if (pchunk != nullptr) { + // already own buffer + if (pchunk->size < size) { + // only if size is not enough, create a bigger buffer copy + chunk_t * tmp = pchunk->duplicate(size); + chunk_t::free(pchunk); + pchunk = tmp; + } + } else { + // create new allocation of requested size + pchunk = chunk_t::alloc(size); + pchunk->len = 0; + pchunk->buf[0] = 0; + } + return *this; + } + + // Trim methods + + /// remove leading and trailing spaces and tabs + string_wr& trim() noexcept { + // do nothing for empty line + if (!pchunk || !pchunk->len) { + return *this; + } + // we own buffer + size_type firstns = 0; + size_type len = pchunk->len; + char_type * buf = pchunk->buf; + for (; + firstns < len && + (buf[firstns] == ' ' || + buf[firstns] == '\t'); + ++firstns) + ; + if (firstns >= len) { + // only whitespace chars in the string + pchunk->len = 0; + pchunk->buf[0] = 0; + return *this; + } + size_type lastns = len - 1; + for (; + //lastns>0 && // this check is not needed - we know that string contains non-empty-space char(s) + (buf[lastns]==' ' || buf[lastns]=='\t'); + --lastns) + ; + size_type newlen = (size_type)(lastns + 1 - firstns); + if (newlen == len) { + // nothing to trim + return *this; + } + if (firstns) { + std::memmove( buf, buf + firstns, newlen*sizeof(char_type) ); + } + buf[newlen] = 0; + pchunk->len = newlen; + return *this; + } + + + /// compact buffer if possible -- free unused buffer space; returned reference is a normal string + string_type& pack() noexcept { + if (pchunk) { + size_type new_size = chunk_t::alignSize(pchunk->len); + if (pchunk->size > new_size) { + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, pchunk->len); + chunk_t::free(pchunk); + pchunk = tmp; + } + } + return *reinterpret_cast(this); + } + + /// move assignment - shared string + /// + string_wr& assign(ro_string_type&& s) noexcept { + LS_COUNT_MOVE_ASSIGN + + if (&s == this || pchunk == s.pchunk) { + // safe self assignment: do nothing + return *this; + } + + if (s.empty()) { + // assign empty string + if (pchunk) { + // reset buffer length to 0, don't free the buffer + pchunk->len = 0; + } + return *this; + } else if (s.pchunk->isShared()) { + // passed string is shared + if (!pchunk) { + // this buffer is null, need to copy + pchunk = s.pchunk->duplicate(); + } else { + // we have buffer, try to figure out if only content may be copied + // this call will copy content if dest buffer has enough capacity, or alloc a copy otherwise + s.pchunk->duplicate_to_owned(pchunk); + } + } else { + // s is not shared, may borrow its buffer instead of copy + if (pchunk) { + // free our buffer + chunk_t::free(pchunk); + } + pchunk = s.pchunk; + s.pchunk = nullptr; + } + return *this; + } + + /// move assignment - owned string + string_wr& assign(string_wr&& s) noexcept { + LS_COUNT_MOVE_ASSIGN + if (&s == this || pchunk == s.pchunk) { + // safe self assignment: do nothing + return *this; + } + // self-assignment prevention + if (pchunk != nullptr) { + chunk_t::free(pchunk); + } + pchunk = s.pchunk; + s.pchunk = nullptr; + return *this; + } + + /// copy assignment + string_wr& assign(const ro_string_type& s) noexcept { + LS_COUNT_COPY_ASSIGN + + if (&s == this || pchunk == s.pchunk) { + // safe self assignment: do nothing + return *this; + } + + if (s.empty()) { + // empty string assignment: just reset len + if (pchunk) + pchunk->len = 0; + } else { + // try reuse this buffer if it has enough capacity, or create duplicate otherwise + s.pchunk->duplicate_to_owned(pchunk); + } + return *this; + } + + /// fragment assignment; correctly covers self-assignment; doesn't check bounds + string_wr& assign(const ro_string_type&s, size_type offset, size_type count) noexcept { + if (s.pchunk && offset < s.pchunk->len) { + s.pchunk->duplicate_to_owned(pchunk, offset, count); + } else { + // assignment of empty string: don't clear the buffer -- typically string_wr is created to do multiple updates + if (pchunk) + pchunk->len = 0; + } + return *this; + } + + /// assign from z-terminated string + string_wr& operator = (const char_type * s) noexcept { + return assign(s); + } + + /// assign from z-terminated string + string_wr& assign(const char_type * s) noexcept { + if (s == nullptr || !*s) { + // assignment of empty string: don't clear the buffer -- typically string_wr is created to do multiple updates + if (pchunk) + pchunk->len = 0; + } else { + size_type count = str_len(s); + if (pchunk != nullptr && pchunk->size >= count) { + // reuse existing buffer + std::memcpy(pchunk->buf, s, count * sizeof(char_type)); + pchunk->len = count; + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(s, count, count); + if (pchunk) { + chunk_t::free(pchunk); + } + pchunk = tmp; + } + } + return *this; + } + + /// assign from char pointer and char count + string_wr& assign(const char_type * s, size_type count) noexcept { + if (s == nullptr || count == 0) { + // assignment of empty string: don't clear the buffer -- typically string_wr is created to do multiple updates + if (pchunk) + pchunk->len = 0; + } else { + if (pchunk != nullptr && pchunk->size >= count) { + // reuse existing buffer + std::memcpy(pchunk->buf, s, count * sizeof(char_type)); + pchunk->len = count; + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(s, count, count); + if (pchunk) { + chunk_t::free(pchunk); + } + pchunk = tmp; + } + } + return *this; + } + + /// Assign utf string from char pointer and char count, perform UTF conversion and invalid unicode codepoints skipping if necessary. + /// Source string must be encoded as utf characters (utf8 for 1-byte utf_type, utf16 for 2-byte utf_type, utf32 for 4-byte utf_type). + /// Converted data will be assigned on this string in format depending on char_type size (utf8/utf16/utf32). + /// s is a start of source utf string + /// count is number of source string elements to convert. + /// Pass `npos` as source string element counter to calculate it for null-term string with str_len + template + string_wr& assignUtf(const utf_type * s, size_type count = npos) noexcept { + if (s == nullptr) { + count = 0; + } else if (count == npos) { + count = str_len(s); + } + if (count == 0) { + // assignment of empty string: don't clear the buffer -- typically string_wr is created to do multiple updates + if (pchunk) + pchunk->len = 0; + } else { + size_type sz = utfConvDestBufferSize(s, s + count); + lUInt32 errorCount = 0; + if (pchunk != nullptr && pchunk->size >= sz) { + // reuse existing buffer + char_type * dst = pchunk->buf; + pchunk->len = utfConvert(s, s+count, dst, dst + pchunk->size, errorCount); + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(sz); + char_type * dst = tmp->buf; + tmp->len = utfConvert(s, s+count, dst, dst + tmp->size, errorCount); + if (pchunk) { + chunk_t::free(pchunk); + } + pchunk = tmp; + } + } + return *this; + } + + /// Append utf string from char pointer and char count, perform UTF conversion and invalid unicode codepoints skipping if necessary. + /// Source string must be encoded as utf characters (utf8 for 1-byte utf_type, utf16 for 2-byte utf_type, utf32 for 4-byte utf_type). + /// Converted data will be assigned on this string in format depending on char_type size (utf8/utf16/utf32). + /// s is a start of source utf string + /// count is number of source string elements to convert. + /// Pass `npos` as source string element counter to calculate it for null-term string with str_len + template + string_wr& appendUtf(const utf_type * s, size_type count = npos) noexcept { + if (empty()) { + return assignUtf(s, count); + } + if (s == nullptr) { + return *this; + } else if (count == npos) { + count = str_len(s); + } + if (count == 0) { + return *this; + } + size_type sz = utfConvDestBufferSize(s, s + count); + lUInt32 errorCount = 0; + if (pchunk->size >= pchunk->len + sz) { + // reuse existing buffer - it is big enough, and not shared + char_type * dst = pchunk->buf + pchunk->len; + pchunk->len += utfConvert(s, s+count, dst, pchunk->buf + pchunk->size, errorCount); + } else { + // create new buffer + chunk_t * tmp = pchunk->duplicate(pchunk->len + sz); + char_type * dst = tmp->buf + tmp->len; + tmp->len += utfConvert(s, s+count, dst, tmp->buf + tmp->size, errorCount); + chunk_t::free(pchunk); + pchunk = tmp; + } + return *this; + } + + /// convert all characters of string to uppercase + string_wr& uppercase() noexcept { + if (pchunk) { + lStr_uppercase(pchunk->buf, pchunk->len); + } + return *this; + } + + /// convert all characters of string to lowercase + string_wr& lowercase() noexcept { + if (pchunk) { + lStr_lowercase(pchunk->buf, pchunk->len); + } + return *this; + } + + /// appends decimal string representation of integer value + string_wr& appendDecimal(lInt64 n) noexcept { + char_type buf[24]; + int i=0; + int negative = 0; + if (n==0) { + return append(1, '0'); + } else if (n<0) + { + negative = 1; + n = -n; + } + for ( ; n; n/=10 ) + { + buf[i++] = '0' + (n % 10); + } + reserve(length() + i + negative); + if (negative) + append(1, '-'); + for (int j=i-1; j>=0; j--) + append(1, buf[j]); + return *this; + } + + /// appends hex string representation of integer value, no leading zeroes + string_wr& appendHex(lUInt64 n) noexcept { + if (n == 0) { + return append(1, '0'); + } + reserve(length() + 16); + bool foundNz = false; + for (int i=0; i<16; i++) { + int digit = (n >> 60) & 0x0F; + if (digit) { + foundNz = true; + } + if (foundNz) { + append(1, (static_cast("0123456789abcdef"[digit])) & 0xFF); + } + n <<= 4; + } + return *this; + } + + /// append fragment from null-terminated string; no validation of input string is performed + string_wr& append(const char_type* s, size_type count) noexcept { + if (count) { + if (!pchunk) { + pchunk = chunk_t::alloc(s, count, count); + } else { + size_type new_len = pchunk->len + count; + if (pchunk->size < new_len) { + // need new buffer: either shared or not enough capacity + // if s points to our own buffer, save data before releasing + bool self_append = (s == pchunk->buf); + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, new_len); + if (self_append) { + // s was our old buffer, now freed; copy from the new tmp which has the old data + std::memcpy(tmp->buf + tmp->len, tmp->buf, sizeof(char_type) * count); + } else { + std::memcpy(tmp->buf + tmp->len, s, sizeof(char_type) * count); + } + tmp->len = new_len; + tmp->buf[new_len] = 0; + chunk_t::free(pchunk); + pchunk = tmp; + } else { + std::memmove(pchunk->buf + pchunk->len, s, sizeof(char_type) * count); + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + } + } + return *this; + } + + /// append null-terminated string + string_wr& append(const char_type* s) noexcept { + if (!s || !*s) { + return *this; + } + size_type count = str_len(s); + if (count) { + append(s, count); + } + return *this; + } + + /// append another string + string_wr& append(const string_wr& s) noexcept { + if (!s.empty()) { + return append(s.pchunk->buf, s.pchunk->len); + } + return *this; + } + + /// append fragment of another string + string_wr& append(const string_wr& s, size_type offset, size_type count) noexcept { + if (!s.empty() && offset < s.pchunk->len) { + if (offset + count > s.pchunk->len) { + count = s.pchunk->len - offset; + } + return append(s.pchunk->buf + offset, count); + } + return *this; + } + + /// append one or more characters + string_wr& append(size_type count, char_type c) noexcept { + if (pchunk) { + size_type new_len = pchunk->len + count; + if (pchunk->size < new_len) { + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, new_len); + for (size_type i = 0; i < count; i++) { + tmp->buf[pchunk->len + i] = c; + } + tmp->len = new_len; + tmp->buf[new_len] = 0; + chunk_t::free(pchunk); + pchunk = tmp; + } else { + // own buffer with enough capacity + for (size_type i = 0; i < count; i++) { + pchunk->buf[pchunk->len + i] = c; + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + } else { + // appending to empty string + pchunk = chunk_t::alloc(count); + for (size_type i = 0; i < count; i++) { + pchunk->buf[i] = c; + } + pchunk->len = count; + pchunk->buf[count] = 0; + } + return *this; + } + + /// append single character + string_wr& operator << (char_type ch) noexcept { return append(1, ch); } + /// append C-string + string_wr& operator << (const char_type * str) noexcept { return append(str); } + /// append string + string_wr& operator << (const string_wr & str) noexcept { return append(str); } + /// append decimal number + string_wr& operator << (const fmt::decimal v) noexcept { return appendDecimal(v.get()); } + /// append hex number + string_wr& operator << (const fmt::hex v) noexcept { return appendHex(v.get()); } + + /// append single character + string_wr& operator += (char_type ch) noexcept { return append(1, ch); } + /// append C-string + string_wr& operator += (const char_type * str) noexcept { return append(str); } + /// append string + string_wr& operator += (const string_wr & str) noexcept { return append(str); } + /// append decimal number + string_wr& operator += (const fmt::decimal v) noexcept { return appendDecimal(v.get()); } + /// append hex number + string_wr& operator += (const fmt::hex v) noexcept { return appendHex(v.get()); } + + + /// insert from char* and size + string_wr& insert(size_type pos, const char_type* s, size_type count) noexcept { + if (!count) { + return *this; + } + if (!pchunk) { + // insert into empty string + pchunk = chunk_t::alloc(s, count, count); + } else { + if (pos > pchunk->len) { + pos = pchunk->len; + } + size_type new_len = pchunk->len + count; + size_type tail_len = pchunk->len - pos; + if (new_len > pchunk->size) { + // create copy + chunk_t* tmp = chunk_t::alloc(new_len); + if (pos > 0) { + // copy head + std::memcpy(tmp->buf, pchunk->buf, sizeof(char_type) * pos); + } + // copy inserted content + std::memcpy(tmp->buf + pos, s, sizeof(char_type) * count); + // copy tail if needed + if (tail_len) { + // copy tail + std::memcpy(tmp->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + tmp->len = new_len; + chunk_t::free(pchunk); + pchunk = tmp; + } else { + // insert in-place + if (tail_len) { + // move tail by count chars + std::memmove(pchunk->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + // copy inserted content + std::memcpy(pchunk->buf + pos, s, sizeof(char_type) * count); + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + return *this; + } + + /// insert from null-terminated cont char* + string_wr& insert(size_type pos, const char_type* s) noexcept { + if (!s || !*s) { + // attempt inserting empty string + return *this; + } + return insert(pos, s, str_len(s)); + } + + /// insert from string (no self-insert protection) + string_wr& insert(size_type pos, const ro_string_type& s) noexcept { + if (s.empty()) { + // attempt inserting empty string + return *this; + } + return insert(pos, s.pchunk->buf, s.pchunk->len); + } + + /// insert one or more characters + string_wr& insert(size_type pos, size_type count, char_type c) noexcept { + if (!count) { + return *this; + } + if (!pchunk) { + // insert into empty string + pchunk = chunk_t::alloc(count); + for (size_type i = 0; i < count; i++) { + pchunk->buf[i] = c; + } + pchunk->len = count; + pchunk->buf[count] = 0; + } else { + if (pos > pchunk->len) { + pos = pchunk->len; + } + size_type new_len = pchunk->len + count; + size_type tail_len = pchunk->len - pos; + if (new_len > pchunk->size) { + // create copy + chunk_t* tmp = chunk_t::alloc(new_len); + if (pos > 0) { + // copy head + std::memcpy(tmp->buf, pchunk->buf, sizeof(char_type) * pos); + } + // copy inserted content + for (size_type i = 0; i < count; i++) { + tmp->buf[pos + i] = c; + } + // copy tail if needed + if (tail_len) { + // copy tail + std::memcpy(tmp->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + tmp->len = new_len; + chunk_t::free(pchunk); + pchunk = tmp; + } else { + // insert in-place + if (tail_len) { + // move tail by count chars + std::memmove(pchunk->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + // copy inserted content + for (size_type i = 0; i < count; i++) { + pchunk->buf[pos + i] = c; + } + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + return *this; + } + + /// replace part of string with count chars from char* + string_wr& replace(size_type pos, size_type n, const char_type * s, size_type count) noexcept { + if (!count) { + return *this; + } + if (empty()) { + return assign(s, count); + } else { + // need to modify + size_type old_len = pchunk->len; + if (pos > old_len) { + pos = old_len; + } + if (pos + n > old_len) { + n = old_len - pos; + } + if (!n) { + // delegate to insert + return insert(pos, s, count); + } + if (pos == old_len) { + // use append + return append(s, count); + } + size_type new_len = old_len + count - n; + size_type tail_len = old_len - (pos + n); + if (new_len > pchunk->size) { + // need to create copy -- no space or has other refs + chunk_t * tmp = chunk_t::alloc(pchunk->buf, new_len, new_len); + // move tail to the end of buffer + std::memmove(tmp->buf + new_len - tail_len, tmp->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + std::memcpy(tmp->buf + pos, s, sizeof(char_type) * count); + // replace this string with new + chunk_t::free(pchunk); + pchunk = tmp; + } else { + // may edit in-place + // move tail to the end of buffer + std::memmove(pchunk->buf + new_len - tail_len, pchunk->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + std::memcpy(pchunk->buf + pos, s, sizeof(char_type) * count); + } + // update length + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + return *this; + } + } + + /// replace part of string with count chars from char* + string_wr& replace(size_type pos, size_type n, size_type count, char_type c) noexcept { + if (!count) { + // nothing to insert + return *this; + } + if (empty()) { + return append(count, c); + } else { + // need to modify + size_type old_len = pchunk->len; + if (pos > old_len) { + pos = old_len; + } + if (pos + n > old_len) { + n = old_len - pos; + } + if (!n) { + // delegate to insert + return insert(pos, count, c); + } + if (pos == old_len) { + // use append + return append(count, c); + } + size_type new_len = old_len + count - n; + size_type tail_len = old_len - (pos + n); + if (new_len > pchunk->size) { + // need to create copy -- no space or has other refs + chunk_t * tmp = chunk_t::alloc(pchunk->buf, new_len, new_len); + // move tail to the end of buffer + std::memmove(tmp->buf + new_len - tail_len, tmp->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + for (size_type i = 0; i < count; i++) { + tmp->buf[pos + i] = c; + } + // replace this string with new + chunk_t::free(pchunk); + pchunk = tmp; + } else { + // may edit in-place + // move tail to the end of buffer + std::memmove(pchunk->buf + new_len - tail_len, pchunk->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + for (size_type i = 0; i < count; i++) { + pchunk->buf[pos + i] = c; + } + } + // update length + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + return *this; + } + } + + /// replace part of string with count chars from char* + string_wr& replace(size_type pos, size_type n, const char_type * s) noexcept { + if (!s || !*s) { + // nothing to insert + return *this; + } + size_type count = str_len(s); + return replace(pos, n, s, count); + } + + /// replace part of string with count chars from char* + string_wr& replace(size_type pos, size_type n, const ro_string_type& s) noexcept { + if (s.empty()) { + // nothing to insert + return *this; + } + return replace(pos, n, s.pchunk->buf, s.pchunk->len); + } + + /// replace part of string with count chars from char* + string_wr& replace(size_type pos, size_type n, const ro_string_type& s, size_type offset, size_type count) noexcept { + if (s.empty()) { + // nothing to insert + return *this; + } + size_type s_len = s.pchunk->len; + if (offset >= s_len) { + // source data indexes outside source string bounds + return *this; + } + // ensure source range is inside string - truncate if needed + if (offset + count > s_len) { + count = s_len - offset; + } + return replace(pos, n, s.pchunk->buf + offset, count); + } + + /// replace all occurences of character replaceWhat with character replaceTo + string_wr& replace(char_type replaceWhat, char_type replaceTo) noexcept { + if (empty()) { + // nothing to replace + return *this; + } + size_type len = pchunk->len; + char_type * buf = pchunk->buf; + for (size_type i = 0; i < len; i++) { + if (buf[i] == replaceWhat) { + // first match found: replace is needed + // own buffer : replace in-place + buf[i++] = replaceTo; + while (i < len) { + if (buf[i] == replaceWhat) { + buf[i] = replaceTo; + } + i++; + } + break; + } + } + return *this; + } + + /// erases fragment from string; if requested fragment exceeds string bounds, it's size is truncated + string_wr& erase(size_type offset, size_type count) noexcept { + if (pchunk && offset < pchunk->len && count) { + if (offset + count > pchunk->len) { + // truncate requested erased fragment size to not exceed bounds + count = pchunk->len - offset; + } + size_type new_len = pchunk->len - count; + size_type tail_start = offset + count; + size_type tail_len = pchunk->len - tail_start; + // erase inplace + if (tail_len) { + std::memmove(pchunk->buf + offset, pchunk->buf + tail_start, sizeof(char_type) * tail_len); + } + pchunk->len = new_len; + pchunk->buf[pchunk->len] = 0; + } + return *this; + } +}; + +template +class string : public string_ro { + friend class string_wr; +public: + // typedefs for STL compatibility + typedef char_type value_type; ///< character type + typedef lUInt32 size_type; ///< size type + typedef lInt32 difference_type; ///< difference type + typedef value_type * pointer; ///< pointer to char type + typedef value_type & reference; ///< reference to char type + typedef const value_type * const_pointer; ///< pointer to const char type + typedef const value_type & const_reference; ///< reference to const char type + + // constant for not found / unspecified position + static const size_type npos = -1; + + // empty string constant + //static const string empty_str; + //static string constexpr empty_str = nullptr; + inline static const string empty_str{}; + + // COW types + typedef string_wr writable_string; ///< writable (owned) string + typedef string_wr& writable_ref; ///< writable (owned) string reference + typedef string_ro ro_string_type; ///< readonly string type + +private: + + using typename string_ro::chunk_t; + using string_ro::pchunk; + +public: + using string_ro::empty; + using string_ro::length; + using string_ro::size; + using string_ro::capacity; + + using string_ro::operator []; + using string_ro::at; + using string_ro::c_str; + //using string_ro::data; + + +public: + string() noexcept = default; + string(const char_type* s, size_type count, size_type reserved) noexcept { + if (count == 0 || s == nullptr || s[0] == 0) { + pchunk = nullptr; + } else { + pchunk = chunk_t::alloc(s, count, reserved); + } + } + string(const char_type* s, size_type count) noexcept { + if (count == 0 || s == nullptr || s[0] == 0) { + pchunk = nullptr; + } else { + pchunk = chunk_t::alloc(s, count, count); + } + } + explicit string(const char_type* s) noexcept { + if (s != nullptr && *s) { + pchunk = chunk_t::alloc(s, str_len(s), 0); + } else { + pchunk = nullptr; + } + } + /// copy constructor + string(const string&s) noexcept { + LS_COUNT_COPY_CONSTR + pchunk = s.pchunk; + intrusive_ptr_add_ref_checknull(pchunk); + } + /// constructor of empty buffer with reserved size + string(size_type size) noexcept { + pchunk = chunk_t::alloc(size); + } + /// move constructor + string(string&&s ) noexcept { + LS_COUNT_MOVE_CONSTR + pchunk = s.pchunk; + s.pchunk = nullptr; + } + /// fragment constructor + string(const string&s, size_type offset, size_type count) noexcept { + if (s.pchunk && offset < s.pchunk->len) { + size_type avail = s.pchunk->len - offset; + if (count > avail) count = avail; + pchunk = chunk_t::alloc(s.pchunk->buf + offset, count, count); + } else { + pchunk = nullptr; + } + } + /// destructor - frees reference + ~string() noexcept { + if (pchunk != nullptr) { + intrusive_ptr_release(pchunk); + } + } + + /// move assignment + string& operator = (string&& s) noexcept { + LS_COUNT_MOVE_ASSIGN + if (this != &s) { + if (pchunk != nullptr) { + intrusive_ptr_release(pchunk); + } + pchunk = s.pchunk; + s.pchunk = nullptr; + } + return *this; + } + /// copy assignment + string& operator = (const string& s) noexcept { + return assign(s); + } + + /// move assignment + string& assign(string&& s) noexcept { + LS_COUNT_MOVE_ASSIGN + if (this != &s) { + if (pchunk != nullptr) { + intrusive_ptr_release(pchunk); + } + pchunk = s.pchunk; + s.pchunk = nullptr; + } + return *this; + } + + /// copy assignment + string& assign(const string& s) noexcept { + LS_COUNT_COPY_ASSIGN + if (&s == this || pchunk == s.pchunk) { + // safe self assignment: do nothing + return *this; + } + if (pchunk != nullptr) { + // ignore self-assignment + intrusive_ptr_release(pchunk); + pchunk = s.pchunk; + if (pchunk) { + // assigned non-empty string + intrusive_ptr_add_ref(pchunk); + } + } else { + // this string is null + pchunk = s.pchunk; + if (pchunk) { + // assigned non-empty string + intrusive_ptr_add_ref(pchunk); + } + } + return *this; + } + + /// fragment assignment; correctly covers self-assignment; doesn't check bounds + string& assign(const ro_string_type&s, size_type offset, size_type count) noexcept { + if (s.pchunk && offset < s.pchunk->len) { + size_type avail = s.pchunk->len - offset; + if (count > avail) count = avail; + chunk_t* tmp = chunk_t::alloc(s.pchunk->buf + offset, count, count); + if (pchunk) { + intrusive_ptr_release(pchunk); + } + pchunk = tmp; + } else { + clear(); + } + return *this; + } + + /// assign from z-terminated string + string& operator = (const char_type * s) noexcept { + return assign(s); + } + + /// assign from z-terminated string + string& assign(const char_type * s) noexcept { + if (s == nullptr || !*s) { + clear(); + } else { + size_type count = str_len(s); + if (pchunk != nullptr && pchunk->isOwn() && pchunk->size >= count && pchunk->size / 2 < count) { + // reuse existing buffer + std::memcpy(pchunk->buf, s, count * sizeof(char_type)); + pchunk->len = count; + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(s, count, count); + if (pchunk) { + intrusive_ptr_release(pchunk); + } + pchunk = tmp; + } + } + } + + /// assign from char pointer and char count + string& assign(const char_type * s, size_type count) noexcept { + if (s == nullptr || !*s || count == 0) { + clear(); + } else { + if (pchunk != nullptr && pchunk->isOwn() && pchunk->size >= count && pchunk->size / 2 < count) { + // reuse existing buffer + std::memcpy(pchunk->buf, s, count * sizeof(char_type)); + pchunk->len = count; + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(s, count, count); + if (pchunk) { + intrusive_ptr_release(pchunk); + } + pchunk = tmp; + } + } + return *this; + } + + /// Assign utf string from char pointer and char count, perform UTF conversion and invalid unicode codepoints skipping if necessary. + /// Source string must be encoded as utf characters (utf8 for 1-byte utf_type, utf16 for 2-byte utf_type, utf32 for 4-byte utf_type). + /// Converted data will be assigned on this string in format depending on char_type size (utf8/utf16/utf32). + /// s is a start of source utf string + /// count is number of source string elements to convert. + /// Pass `npos` as source string element counter to calculate it for null-term string with str_len + template + string& assignUtf(const utf_type * s, size_type count = npos) noexcept { + if (s == nullptr) { + count = 0; + } else if (count == npos) { + count = str_len(s); + } + if (count == 0) { + clear(); + } else { + size_type sz = utfConvDestBufferSize(s, s + count); + lUInt32 errorCount = 0; + if (pchunk != nullptr && pchunk->size >= sz && pchunk->isOwn()) { + // reuse existing buffer - it is big enough, and not shared + char_type * dst = pchunk->buf; + pchunk->len = utfConvert(s, s+count, dst, dst + pchunk->size, errorCount); + } else { + // create new buffer + chunk_t * tmp = chunk_t::alloc(sz); + char_type * dst = tmp->buf; + tmp->len = utfConvert(s, s+count, dst, dst + tmp->size, errorCount); + if (pchunk) { + intrusive_ptr_release(pchunk); + } + pchunk = tmp; + } + } + return *this; + } + + /// Append utf string from char pointer and char count, perform UTF conversion and invalid unicode codepoints skipping if necessary. + /// Source string must be encoded as utf characters (utf8 for 1-byte utf_type, utf16 for 2-byte utf_type, utf32 for 4-byte utf_type). + /// Converted data will be assigned on this string in format depending on char_type size (utf8/utf16/utf32). + /// s is a start of source utf string + /// count is number of source string elements to convert. + /// Pass `npos` as source string element counter to calculate it for null-term string with str_len + template + string& appendUtf(const utf_type * s, size_type count = npos) noexcept { + if (empty()) { + return assignUtf(s, count); + } + if (s == nullptr) { + return *this; + } else if (count == npos) { + count = str_len(s); + } + if (count == 0) { + return *this; + } + size_type sz = utfConvDestBufferSize(s, s + count); + lUInt32 errorCount = 0; + if (pchunk->size >= pchunk->len + sz && pchunk->isOwn()) { + // reuse existing buffer - it is big enough, and not shared + char_type * dst = pchunk->buf + pchunk->len; + pchunk->len += utfConvert(s, s+count, dst, pchunk->buf + pchunk->size, errorCount); + } else { + // create new buffer + chunk_t * tmp = pchunk->duplicate(pchunk->len + sz); + char_type * dst = tmp->buf + tmp->len; + tmp->len += utfConvert(s, s+count, dst, tmp->buf + tmp->size, errorCount); + if (pchunk) { + intrusive_ptr_release(pchunk); + } + pchunk = tmp; + } + return *this; + } + + /// sets value to null string, freeing buffer + string& clear() noexcept { + if (pchunk != nullptr) { + intrusive_ptr_release(pchunk); + pchunk = nullptr; + } + return *this; + } + + /// resets length to zero, preparing to modification with reserved size + writable_string& reset(size_type size) noexcept { + if (pchunk == nullptr) { + // buffer is allocated and has capacity at least size + pchunk = chunk_t::alloc(size); + } else { + // this string is non-empty + if (pchunk->isOwn() && pchunk->size >= size) { + // this is already our own string with enough capacity -- just reset length + pchunk->len = 0; + } else { + // this string has not enough capacity for size, clear and alloc new + intrusive_ptr_release(pchunk); + pchunk = chunk_t::alloc(size); + } + } + return *reinterpret_cast(this); + } + + /// ensure that this string owns buffer and it has at least size capacity + writable_string& reserve(size_type size) noexcept { + if (size == 0) { + // reserve(0) is a non-binding request, just treat this empty string as owned + return *reinterpret_cast(this); + } + if (pchunk != nullptr) { + if (pchunk->isOwn()) { + // already own buffer + if (pchunk->size < size) { + // only if size is not enough, create a bigger buffer copy + chunk_t * tmp = chunk_t::alloc(size); + tmp->len = pchunk->len; + std::memcpy(tmp->buf, pchunk->buf, pchunk->len); + tmp->buf[tmp->len] = 0; + chunk_t::free(pchunk); + pchunk = tmp; + } + } else { + // we don't own the string, need to create a copy + if (size < pchunk->len) { + // when requested size is smaller than existing string len, ensure we will not loss chars + size = pchunk->len; + } + chunk_t * tmp = chunk_t::alloc(size); + tmp->len = pchunk->len; + std::memcpy(tmp->buf, pchunk->buf, pchunk->len); + tmp->buf[tmp->len] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + } else { + // create new allocation of requested size + pchunk = chunk_t::alloc(size); + pchunk->len = 0; + pchunk->buf[0] = 0; + } + return *reinterpret_cast(this); + } + + /// get writable reference for own string - create a copy only if refcount > 1 + writable_string& writableRef() { + if (pchunk && pchunk->isShared()) { + // create a copy with refcount==1 + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, pchunk->len); + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + return *reinterpret_cast(this); + } + + /// get writable reference for own string - create a copy if refcount > 1 + /// if own copy has to be created, reserve size + writable_string& writableRef(size_type size) { + if (pchunk && pchunk->isShared()) { + // create a copy with refcount==1 + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, size); + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + return *reinterpret_cast(this); + } + + /// resize string to specified length, filling new positions with e if expanded + writable_string& resize(size_type size, char_type e = 0) noexcept { + if (pchunk == nullptr) { + // empty string: allocate new buffer + pchunk = chunk_t::alloc(size); + for (size_type i = 0; i < size; i++) + pchunk->buf[i] = e; + pchunk->len = size; + pchunk->buf[size] = 0; + } else { + // non-empty string + size_type oldlen = pchunk->len; + if (pchunk->isOwn() && pchunk->size >= size) { + // own buffer with enough capacity: just adjust length + if (size > oldlen) { + for (size_type i = oldlen; i < size; i++) + pchunk->buf[i] = e; + } + pchunk->len = size; + pchunk->buf[size] = 0; + } else { + // need new buffer + chunk_t * tmp = chunk_t::alloc(pchunk->buf, oldlen < size ? oldlen : size, size); + if (size > oldlen) { + for (size_type i = oldlen; i < size; i++) + tmp->buf[i] = e; + } + tmp->len = size; + tmp->buf[size] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + } + return *reinterpret_cast(this); + } + + /// convert all characters of string to uppercase + string& uppercase() noexcept { + if (pchunk) { + writableRef().uppercase(); + } + return *this; + } + + /// convert all characters of string to lowercase + string& lowercase() noexcept { + if (pchunk) { + writableRef().lowercase(); + } + return *this; + } + + /// remove leading and trailing spaces and tabs + string& trim() noexcept { + // do nothing for empty line + if (!pchunk || !pchunk->len) { + return *this; + } + size_type firstns = 0; + size_type len = pchunk->len; + char_type * buf = pchunk->buf; + for (; + firstns < len && + (buf[firstns] == ' ' || + buf[firstns] == '\t'); + ++firstns) + ; + if (firstns >= len) { + // only whitespace chars in the string + return clear(); + } + size_type lastns = len - 1; + for (; + //lastns>0 && // this check is not needed - we know that string contains non-empty-space char(s) + (buf[lastns]==' ' || buf[lastns]=='\t'); + --lastns) + ; + size_type newlen = (size_type)(lastns + 1 - firstns); + if (newlen == len) { + // nothing to trim + return *this; + } + if (pchunk->isOwn()) { + if (firstns) { + std::memmove( buf, buf + firstns, newlen*sizeof(char_type) ); + } + buf[newlen] = 0; + pchunk->len = newlen; + } else { + chunk_t * tmp = chunk_t::alloc(buf + firstns, newlen, newlen); + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + return *this; + } + + /// trim non-alphanumeric characters from beginning and end of string + string& trimNonAlpha() noexcept { + // TODO: implement + return *this; + } + + /// erases fragment from string; if requested fragment exceeds string bounds, it's size is truncated + string& erase(size_type offset, size_type count) noexcept { + if (pchunk && offset < pchunk->len && count) { + if (offset + count > pchunk->len) { + // truncate requested erased fragment size to not exceed bounds + count = pchunk->len - offset; + } + size_type new_len = pchunk->len - count; + size_type tail_start = offset + count; + size_type tail_len = pchunk->len - tail_start; + if (pchunk->isOwn()) { + // erase inplace + if (tail_len) { + std::memmove(pchunk->buf + offset, pchunk->buf + tail_start, sizeof(char_type) * tail_len); + } + } else { + // create a copy with fragment erased + chunk_t * tmp = chunk_t::alloc(new_len); + if (offset) { + // something left in beginning of string + std::memcpy(tmp->buf, pchunk->buf, sizeof(char_type) * offset); + } + if (tail_len) { + std::memcpy(tmp->buf + offset, pchunk->buf + tail_start, sizeof(char_type) * tail_len); + } + // free old ref and use copy + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + pchunk->len = new_len; + pchunk->buf[pchunk->len] = 0; + } + return *this; + } + + /// appends decimal string representation of integer value + string& appendDecimal(lInt64 n) noexcept { + char_type buf[24]; + int i=0; + int negative = 0; + if (n==0) { + return append(1, '0'); + } else if (n<0) + { + negative = 1; + n = -n; + } + for ( ; n; n/=10 ) + { + buf[i++] = '0' + (n % 10); + } + reserve(length() + i + negative); + if (negative) + append(1, '-'); + for (int j=i-1; j>=0; j--) + append(1, buf[j]); + return *this; + } + + /// appends hex string representation of integer value, no leading zeroes + string& appendHex(lUInt64 n) noexcept { + if (n == 0) { + return append(1, '0'); + } + reserve(length() + 16); + bool foundNz = false; + for (int i=0; i<16; i++) { + int digit = (n >> 60) & 0x0F; + if (digit) { + foundNz = true; + } + if (foundNz) { + append(1, (static_cast("0123456789abcdef"[digit])) & 0xFF); + } + n <<= 4; + } + return *this; + } + + /// append fragment from null-terminated string; no validation of input string is performed + string& append(const char_type* s, size_type count) noexcept { + if (count) { + if (!pchunk) { + pchunk = chunk_t::alloc(s, count, count); + } else { + size_type new_len = pchunk->len + count; + if (pchunk->isShared() || pchunk->size < new_len) { + // need new buffer: either shared or not enough capacity + // if s points to our own buffer, save data before releasing + bool self_append = (s == pchunk->buf); + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, new_len); + if (self_append) { + // s was our old buffer, now freed; copy from the new tmp which has the old data + std::memcpy(tmp->buf + tmp->len, tmp->buf, sizeof(char_type) * count); + } else { + std::memcpy(tmp->buf + tmp->len, s, sizeof(char_type) * count); + } + tmp->len = new_len; + tmp->buf[new_len] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + std::memmove(pchunk->buf + pchunk->len, s, sizeof(char_type) * count); + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + } + } + return *this; + } + + /// append null-terminated string + string& append(const char_type* s) noexcept { + if (!s || !*s) { + return *this; + } + size_type count = str_len(s); + if (count) { + append(s, count); + } + return *this; + } + + /// append another string + string& append(const ro_string_type& s) noexcept { + if (!s.empty()) { + return append(s.pchunk->buf, s.pchunk->len); + } + return *this; + } + + /// append fragment of another string + string& append(const ro_string_type& s, size_type offset, size_type count) noexcept { + if (!s.empty() && offset < s.pchunk->len) { + if (offset + count > s.pchunk->len) { + count = s.pchunk->len - offset; + } + return append(s.pchunk->buf + offset, count); + } + return *this; + } + + /// append one or more characters + string& append(size_type count, char_type c) noexcept { + if (pchunk) { + size_type new_len = pchunk->len + count; + if (pchunk->isShared() || pchunk->size < new_len) { + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, new_len); + for (size_type i = 0; i < count; i++) { + tmp->buf[pchunk->len + i] = c; + } + tmp->len = new_len; + tmp->buf[new_len] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + // own buffer with enough capacity + for (size_type i = 0; i < count; i++) { + pchunk->buf[pchunk->len + i] = c; + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + } else { + // appending to empty string + pchunk = chunk_t::alloc(count); + for (size_type i = 0; i < count; i++) { + pchunk->buf[i] = c; + } + pchunk->len = count; + pchunk->buf[count] = 0; + } + return *this; + } + + /// append single character + string& operator << (char_type ch) noexcept { return append(1, ch); } + /// append C-string + string& operator << (const char_type * str) noexcept { return append(str); } + /// append string + string& operator << (const string & str) noexcept { return append(str); } + /// append decimal number + string& operator << (const fmt::decimal v) noexcept { return appendDecimal(v.get()); } + /// append hex number + string& operator << (const fmt::hex v) noexcept { return appendHex(v.get()); } + + /// append single character + string& operator += (char_type ch) noexcept { return append(1, ch); } + /// append C-string + string& operator += (const char_type * str) noexcept { return append(str); } + /// append string + string& operator += (const string & str) noexcept { return append(str); } + /// append decimal number + string& operator += (const fmt::decimal v) noexcept { return appendDecimal(v.get()); } + /// append hex number + string& operator += (const fmt::hex v) noexcept { return appendHex(v.get()); } + + /// insert from char* and size + string& insert(size_type pos, const char_type* s, size_type count) noexcept { + if (!count) { + return *this; + } + if (!pchunk) { + // insert into empty string + pchunk = chunk_t::alloc(s, count, count); + } else { + if (pos > pchunk->len) { + pos = pchunk->len; + } + size_type new_len = pchunk->len + count; + size_type tail_len = pchunk->len - pos; + if (pchunk->isShared() || new_len > pchunk->size) { + // create copy + chunk_t* tmp = chunk_t::alloc(new_len); + if (pos > 0) { + // copy head + std::memcpy(tmp->buf, pchunk->buf, sizeof(char_type) * pos); + } + // copy inserted content + std::memcpy(tmp->buf + pos, s, sizeof(char_type) * count); + // copy tail if needed + if (tail_len) { + // copy tail + std::memcpy(tmp->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + tmp->len = new_len; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + // insert in-place + if (tail_len) { + // move tail by count chars + std::memmove(pchunk->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + // copy inserted content + std::memcpy(pchunk->buf + pos, s, sizeof(char_type) * count); + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + return *this; + } + + /// insert from null-terminated cont char* + string& insert(size_type pos, const char_type* s) noexcept { + if (!s || !*s) { + // attempt inserting empty string + return *this; + } + return insert(pos, s, str_len(s)); + } + + /// insert from string (no self-insert protection) + string& insert(size_type pos, const ro_string_type& s) noexcept { + if (s.empty()) { + // attempt inserting empty string + return *this; + } + return insert(pos, s.pchunk->buf, s.pchunk->len); + } + + /// insert one or more characters + string& insert(size_type pos, size_type count, char_type c) noexcept { + if (!count) { + return *this; + } + if (!pchunk) { + // insert into empty string + pchunk = chunk_t::alloc(count); + for (size_type i = 0; i < count; i++) { + pchunk->buf[i] = c; + } + pchunk->len = count; + pchunk->buf[count] = 0; + } else { + if (pos > pchunk->len) { + pos = pchunk->len; + } + size_type new_len = pchunk->len + count; + size_type tail_len = pchunk->len - pos; + if (pchunk->isShared() || new_len > pchunk->size) { + // create copy + chunk_t* tmp = chunk_t::alloc(new_len); + if (pos > 0) { + // copy head + std::memcpy(tmp->buf, pchunk->buf, sizeof(char_type) * pos); + } + // copy inserted content + for (size_type i = 0; i < count; i++) { + tmp->buf[pos + i] = c; + } + // copy tail if needed + if (tail_len) { + // copy tail + std::memcpy(tmp->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + tmp->len = new_len; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + // insert in-place + if (tail_len) { + // move tail by count chars + std::memmove(pchunk->buf + pos + count, pchunk->buf + pos, sizeof(char_type) * tail_len); + } + // copy inserted content + for (size_type i = 0; i < count; i++) { + pchunk->buf[pos + i] = c; + } + } + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + } + return *this; + } + + /// compact buffer if possible -- free unused buffer space + string& pack() noexcept { + if (pchunk) { + size_type new_size = chunk_t::alignSize(pchunk->len); + if (pchunk->size > new_size) { + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, pchunk->len); + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + } + return *this; + } + + /// replace part of string with count chars from char* + string& replace(size_type pos, size_type n, const char_type * s, size_type count) noexcept { + if (!count) { + return *this; + } + if (empty()) { + return assign(s, count); + } else { + // need to modify + size_type old_len = pchunk->len; + if (pos > old_len) { + pos = old_len; + } + if (pos + n > old_len) { + n = old_len - pos; + } + if (!n) { + // delegate to insert + return insert(pos, s, count); + } + if (pos == old_len) { + // use append + return append(s, count); + } + size_type new_len = old_len + count - n; + size_type tail_len = old_len - (pos + n); + if (new_len > pchunk->size || pchunk->isShared()) { + // need to create copy -- no space or has other refs + chunk_t * tmp = chunk_t::alloc(pchunk->buf, new_len, new_len); + // move tail to the end of buffer + std::memmove(tmp->buf + new_len - tail_len, tmp->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + std::memcpy(tmp->buf + pos, s, sizeof(char_type) * count); + // replace this string with new + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + // may edit in-place + // move tail to the end of buffer + std::memmove(pchunk->buf + new_len - tail_len, pchunk->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + std::memcpy(pchunk->buf + pos, s, sizeof(char_type) * count); + } + // update length + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + return *this; + } + } + + /// replace part of string with count chars from char* + string& replace(size_type pos, size_type n, size_type count, char_type c) noexcept { + if (!count) { + // nothing to insert + return *this; + } + if (empty()) { + return append(count, c); + } else { + // need to modify + size_type old_len = pchunk->len; + if (pos > old_len) { + pos = old_len; + } + if (pos + n > old_len) { + n = old_len - pos; + } + if (!n) { + // delegate to insert + return insert(pos, count, c); + } + if (pos == old_len) { + // use append + return append(count, c); + } + size_type new_len = old_len + count - n; + size_type tail_len = old_len - (pos + n); + if (new_len > pchunk->size || pchunk->isShared()) { + // need to create copy -- no space or has other refs + chunk_t * tmp = chunk_t::alloc(pchunk->buf, new_len, new_len); + // move tail to the end of buffer + std::memmove(tmp->buf + new_len - tail_len, tmp->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + for (size_type i = 0; i < count; i++) { + tmp->buf[pos + i] = c; + } + // replace this string with new + intrusive_ptr_release(pchunk); + pchunk = tmp; + } else { + // may edit in-place + // move tail to the end of buffer + std::memmove(pchunk->buf + new_len - tail_len, pchunk->buf + old_len - tail_len, sizeof(char_type) * tail_len); + // fill gap with inserted string + for (size_type i = 0; i < count; i++) { + pchunk->buf[pos + i] = c; + } + } + // update length + pchunk->len = new_len; + pchunk->buf[new_len] = 0; + return *this; + } + } + + /// replace part of string with count chars from char* + string& replace(size_type pos, size_type n, const char_type * s) noexcept { + if (!s || !*s) { + // nothing to insert + return *this; + } + size_type count = str_len(s); + return replace(pos, n, s, count); + } + + /// replace part of string with count chars from char* + string& replace(size_type pos, size_type n, const ro_string_type& s) noexcept { + if (s.empty()) { + // nothing to insert + return *this; + } + return replace(pos, n, s.pchunk->buf, s.pchunk->len); + } + + /// replace part of string with count chars from char* + string& replace(size_type pos, size_type n, const ro_string_type& s, size_type offset, size_type count) noexcept { + if (s.empty()) { + // nothing to insert + return *this; + } + size_type s_len = s.pchunk->len; + if (offset >= s_len) { + // source data indexes outside source string bounds + return *this; + } + // ensure source range is inside string - truncate if needed + if (offset + count > s_len) { + count = s_len - offset; + } + return replace(pos, n, s.pchunk->buf + offset, count); + } + + /// replace all occurences of character replaceWhat with character replaceTo + string& replace(char_type replaceWhat, char_type replaceTo) noexcept { + if (empty()) { + // nothing to replace + return *this; + } + size_type len = pchunk->len; + char_type * buf = pchunk->buf; + for (size_type i = 0; i < len; i++) { + if (buf[i] == replaceWhat) { + // first match found: replace is needed + if (pchunk->isOwn()) { + // own buffer : replace in-place + buf[i++] = replaceTo; + while (i < len) { + if (buf[i] == replaceWhat) { + buf[i] = replaceTo; + } + i++; + } + } else { + // need to create a copy + chunk_t * tmp = chunk_t::alloc(pchunk->buf, pchunk->len, pchunk->len); + // switch to new buffer + buf = tmp->buf; + // replace first char + buf[i++] = replaceTo; + while (i < len) { + if (buf[i] == replaceWhat) { + buf[i] = replaceTo; + } + i++; + } + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + break; + } + } + return *this; + } + + /// return C-style null-terminated string pointer + const char_type * data() const noexcept { + return c_str(); + } + + /// return modifable C-style null-terminated string pointer; for empty string returns pointer to fake empty z-string buffer + char_type * data() noexcept { + if (pchunk == nullptr) { + return reinterpret_cast(&fake_null_buffer_32); + } else { + lock(pchunk->len); + // enforce null-termination + const_cast(pchunk)->buf[pchunk->len] = 0; + return pchunk->buf; + } + } + + /// ensures that reference count is 1; if string is null or we own it, do nothing + /// only used from data() ? what is a better place for it? + void lock( size_type newsize ) noexcept { + if (pchunk) { + // string is not null + if (pchunk->isShared()) { + // if we don't own string, make a copy + if (newsize < pchunk->len) { + // ensure all chars from old string were copied + newsize = pchunk->len; + } + chunk_t * tmp = chunk_t::alloc(newsize); + tmp->len = pchunk->len; + std::memcpy(tmp->buf, pchunk->buf, tmp->len); + tmp->buf[tmp->len] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + } + } + + + + /// returns pointer to modifable string buffer + char_type * modify() noexcept { + if (!pchunk) { + // allocate small buffer + pchunk = chunk_t::alloc(8); + } else { + // non-empty string + // string is not null + if (pchunk->isShared()) { + chunk_t * tmp = chunk_t::alloc(pchunk->len); + tmp->len = pchunk->len; + std::memcpy(tmp->buf, pchunk->buf, tmp->len); + tmp->buf[tmp->len] = 0; + intrusive_ptr_release(pchunk); + pchunk = tmp; + } + } + return pchunk->buf; + } + + /// returns substring starting with position pos, and specified count of chars (pass npos to copy till end of string) + string substr(size_type pos, size_type count = npos) const noexcept { + string tmp; + if (!pchunk || pos >= pchunk->len) { + return tmp; + } + size_type sz = pchunk->len - pos; + if (count > sz) { + count = sz; + } + tmp.pchunk = chunk_t::alloc(pchunk->buf + pos, count, count); + return tmp; + } + + /// swaps content of two strings, by swapping of pointers + void swap(string & s) { + chunk_t * tmp = pchunk; + pchunk = s.pchunk; + s.pchunk = tmp; + } + + /// constructs string representation of integer + static string itoa( int n ) { + char_type buf[16]; + size_type i=0; + bool negative = false; + if (n==0) { + buf[i++] = '0'; + //cs8("0"); + } else { + if (n<0) { + negative = true; + n = -n; + } + for ( ; n; n/=10 ) { + buf[i++] = '0' + (n%10); + } + } + if (negative) + buf[i++] = '-'; + // reverse string + for (size_type j = 0; j < i/2; j++) { + char_type tmp = buf[j]; + buf[j] = buf[i - 1 - j]; + buf[i - 1 - j] = tmp; + } + return string(buf, i); + } + /// constructs string representation of unsigned integer + static string itoa( unsigned int n ) { + char_type buf[16]; + size_type i=0; + if (n==0) { + buf[i++] = '0'; + //cs8("0"); + } else { + for ( ; n; n/=10 ) { + buf[i++] = '0' + (n%10); + } + } + // reverse string + for (size_type j = 0; j < i/2; j++) { + char_type tmp = buf[j]; + buf[j] = buf[i - 1 - j]; + buf[i - 1 - j] = tmp; + } + return string(buf, i); + } + // constructs string representation of 64 bit integer + static string itoa( lInt64 n ) { + char_type buf[24]; + size_type i=0; + bool negative = false; + if (n==0) { + buf[i++] = '0'; + //cs8("0"); + } else { + if (n<0) { + negative = true; + n = -n; + } + for ( ; n; n/=10 ) { + buf[i++] = '0' + (n%10); + } + } + if (negative) + buf[i++] = '-'; + // reverse string + for (size_type j = 0; j < i/2; j++) { + char_type tmp = buf[j]; + buf[j] = buf[i - 1 - j]; + buf[i - 1 - j] = tmp; + } + return string(buf, i); + } + + +private: + //chunk_t * pchunk {nullptr}; +}; + +// template +// const string string::empty_str{}; + +typedef lstring_chunk_t lstring8_chunk_t; +typedef lstring_chunk_t lstring16_chunk_t; +typedef lstring_chunk_t lstring32_chunk_t; + +typedef string lString8; +typedef string lString16; +typedef string lString32; + + +} + + +#endif // LVSTRING2_H diff --git a/tests/src/lvstring2test.cpp b/tests/src/lvstring2test.cpp new file mode 100644 index 0000000000..112ad979af --- /dev/null +++ b/tests/src/lvstring2test.cpp @@ -0,0 +1,3124 @@ +#include "lvstring2test.h" +#include + +namespace lv { + + + + +static int test_errors = 0; +#define TCHECK(cond) do { if (!(cond)) { printf("LS FAIL line %d: %s\n", __LINE__, #cond); test_errors++; } } while(0) + +#ifdef DEBUG_TRACK_LSTRING2_ALLOC + #define DUMP_ALLOC_STATS(x) ls_alloc_stats.dump(x); +#else + #define DUMP_ALLOC_STATS(x) +#endif + +void test_lstring2_chunks() { + + // struct size checks + printf("sizeof(lstring8_chunk_t) = %lu\n", sizeof(lstring8_chunk_t)); + printf("sizeof(lstring16_chunk_t) = %lu\n", sizeof(lstring16_chunk_t)); + printf("sizeof(lstring32_chunk_t) = %lu\n", sizeof(lstring32_chunk_t)); + TCHECK(sizeof(lstring8_chunk_t) == sizeof(lstring16_chunk_t)); + TCHECK(sizeof(lstring16_chunk_t) == sizeof(lstring32_chunk_t)); + TCHECK(sizeof(lstring8_chunk_t) == 12); + + //std::atomic_int counter{0}; + //counter.fetch_add(); + + TCHECK(lstring8_chunk_t::alloc_align_bytes == 16); + TCHECK(lstring8_chunk_t::min_size == 3); + TCHECK(lstring8_chunk_t::size_align == 16); + + TCHECK(lstring16_chunk_t::alloc_align_bytes == 16); + TCHECK(lstring16_chunk_t::min_size == 1); + TCHECK(lstring16_chunk_t::size_align == 8); + + TCHECK(lstring32_chunk_t::alloc_align_bytes == 16); + TCHECK(lstring32_chunk_t::min_size == 0); + TCHECK(lstring32_chunk_t::size_align == 4); + + TCHECK(lstring8_chunk_t::alignSize(0) == 4 - 1); + TCHECK(lstring8_chunk_t::alignSize(1) == 4 - 1); + TCHECK(lstring8_chunk_t::alignSize(2) == 4 - 1); + TCHECK(lstring8_chunk_t::alignSize(3) == 4 - 1); + TCHECK(lstring8_chunk_t::alignSize(4) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(5) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(6) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(7) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(11) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(12) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(13) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(15) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(16) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(16+3) == 16+3); + TCHECK(lstring8_chunk_t::alignSize(16+4) == 16+16+3); + + lstring8_chunk_t * chunk8_1 = lstring8_chunk_t::alloc(25); + TCHECK(chunk8_1->size == 32+3); + TCHECK(chunk8_1->len == 0); + TCHECK(chunk8_1->refCount == 1); + TCHECK(chunk8_1->buf[0] == 0); + for (unsigned i = 0; i < 25; i++) { + chunk8_1->buf[i] = 'a'; + chunk8_1->buf[i + 1] = 0; + } + chunk8_1->len = 25; + + chunk8_1->testAddRef(); + chunk8_1->testReleaseRef(); + + lstring8_chunk_t::free(chunk8_1); + + lstring8_chunk_t * chunk8_2 = lstring8_chunk_t::alloc("abcdefg", 4, 10); + TCHECK(chunk8_2->size == 16+3); + TCHECK(chunk8_2->len == 4); + TCHECK(chunk8_2->refCount == 1); + TCHECK(chunk8_2->buf[0] == 'a' && chunk8_2->buf[1] == 'b' && chunk8_2->buf[2] == 'c' && chunk8_2->buf[3] == 'd' && chunk8_2->buf[4] == 0); + lstring8_chunk_t::free(chunk8_2); + + lstring16_chunk_t * chunk16_1 = lstring16_chunk_t::alloc(25); + TCHECK(chunk16_1->size == 24+1); + TCHECK(chunk16_1->len == 0); + TCHECK(chunk16_1->refCount == 1); + TCHECK(chunk16_1->buf[0] == 0); + for (unsigned i = 0; i < 25; i++) { + chunk16_1->buf[i] = 'b'; + chunk16_1->buf[i + 1] = 0; + } + chunk16_1->len = 25; + lstring16_chunk_t::free(chunk16_1); + + lstring16_chunk_t * chunk16_2 = lstring16_chunk_t::alloc(u"bcdefghi", 4, 10); + TCHECK(chunk16_2->size == 16+1); + TCHECK(chunk16_2->len == 4); + TCHECK(chunk16_2->refCount == 1); + TCHECK(chunk16_2->buf[0] == u'b' && chunk16_2->buf[1] == u'c' && chunk16_2->buf[2] == u'd' && chunk16_2->buf[3] == u'e'); + intrusive_ptr_add_ref(chunk16_2); + TCHECK(chunk16_2->getRefCount() == 2); + intrusive_ptr_release(chunk16_2); + TCHECK(chunk16_2->getRefCount() == 1); + intrusive_ptr_release(chunk16_2); + // released automatically + //lstring16_chunk_t::free(chunk16_2); + + lstring32_chunk_t * chunk32_1 = lstring32_chunk_t::alloc(25); + TCHECK(chunk32_1->size == 28); + TCHECK(chunk32_1->len == 0); + TCHECK(chunk32_1->refCount == 1); + TCHECK(chunk32_1->buf[0] == 0); + for (unsigned i = 0; i < 25; i++) { + chunk32_1->buf[i] = 'b'; + chunk32_1->buf[i + 1] = 0; + } + chunk32_1->len = 25; + //lstring32_chunk_t::free(chunk32_1); + intrusive_ptr_release(chunk32_1); + + lstring32_chunk_t * chunk32_2 = lstring32_chunk_t::alloc(U"cdefghijkl", 4, 10); + TCHECK(chunk32_2->size == 12); + TCHECK(chunk32_2->len == 4); + TCHECK(chunk32_2->refCount == 1); + TCHECK(chunk32_2->buf[0] == U'c' && chunk32_2->buf[1] == U'd' && chunk32_2->buf[2] == U'e' && chunk32_2->buf[3] == U'f'); + lstring32_chunk_t::free(chunk32_2); + +} + +void test_lstring8() { + printf("running test_lstring8()\n"); + DUMP_ALLOC_STATS("Entering test_lstring8()") + + // --- Default construction --- + lString8 s {}; + TCHECK(s.capacity() == 0); + TCHECK(s.size() == 0); + TCHECK(s.length() == 0); + TCHECK(s.empty()); + + // --- C-string construction --- + lString8 s2 {"qwerty"}; + TCHECK(s2.capacity() >= 6); + TCHECK(s2.size() >= 6); + TCHECK(s2.length() == 6); + TCHECK(!s2.empty()); + + // --- C-string with count + reserved --- + lString8 s3 {"qwerty", 3, 100}; + TCHECK(s3.capacity() >= 100); + TCHECK(s3.size() == 3); + TCHECK(s3.length() == 3); + TCHECK(!s3.empty()); + + // --- Move construction --- + lString8 s4 = lString8("move"); + TCHECK(s4.length() == 4); + s4 = lString8("move consturctor assignment"); + TCHECK(s4.length() == 27); + + // --- Copy assignment --- + s2 = s4; + TCHECK(s2 == s4); + TCHECK(s2.length() == s4.length()); + + // --- Clear --- + DUMP_ALLOC_STATS("before clear") + s2.clear(); + DUMP_ALLOC_STATS("after clear") + TCHECK(s2.empty()); + TCHECK(s2.length() == 0); + TCHECK(s2.capacity() == 0); + + // --- Reset --- + s4.reset(500); + DUMP_ALLOC_STATS("after reset") + TCHECK(s4.empty()); + TCHECK(s4.length() == 0); + TCHECK(s4.capacity() >= 500); + + // --- Reset on empty --- + lString8 s_empty; + s_empty.reset(50); + TCHECK(s_empty.empty()); + TCHECK(s_empty.capacity() >= 50); + + // --- Resize (expands length, fills with char) --- + lString8 s_rsz {"hi"}; + s_rsz.resize(5, 'x'); + TCHECK(s_rsz.length() == 5); + TCHECK(s_rsz[0] == 'h'); + TCHECK(s_rsz[1] == 'i'); + TCHECK(s_rsz[2] == 'x'); + TCHECK(s_rsz[3] == 'x'); + TCHECK(s_rsz[4] == 'x'); + + // --- Resize to smaller (truncates length) --- + lString8 s_rsz2 {"hello world"}; + s_rsz2.resize(3); + TCHECK(s_rsz2.length() == 3); + TCHECK(s_rsz2[0] == 'h'); + TCHECK(s_rsz2[1] == 'e'); + TCHECK(s_rsz2[2] == 'l'); + + // --- Resize empty string --- + lString8 s_rsz3; + s_rsz3.resize(4, 'z'); + TCHECK(s_rsz3.length() == 4); + TCHECK(s_rsz3[0] == 'z'); + TCHECK(s_rsz3[3] == 'z'); + + // --- Comparison (string vs string) --- + lString8 s5 {"abc"}; + lString8 s6 {"abc"}; + lString8 s7 {"Abc"}; + DUMP_ALLOC_STATS("before compare") + TCHECK(s5 == s6); + TCHECK(s5 <= s6); + TCHECK(s5 >= s6); + TCHECK(s5 > s7); + TCHECK(s7 < s5); + TCHECK(s5 >= s7); + TCHECK(s7 <= s5); + TCHECK(s5 != s7); + + // --- Comparison (string vs c-string) --- + TCHECK(s5 == "abc"); + TCHECK(s5 > "ab"); + TCHECK(s5 < "abcd"); + TCHECK(s5 != "xyz"); + TCHECK(s5 <= "abc"); + TCHECK(s5 >= "abc"); + TCHECK(s5 <= "abd"); + TCHECK(s5 >= "abb"); + + // --- Comparison with empty --- + lString8 s_empty2; + TCHECK(s_empty2 == ""); + TCHECK(s_empty2 == nullptr); + TCHECK(s_empty2 < "a"); + TCHECK(s5 > ""); + + // --- Comparison with nullptr --- + TCHECK(s_empty2 == nullptr); + TCHECK(s5 != nullptr); + + // --- Self-assignment --- + lString8 s_self {"self"}; + lString8 &ref_self = s_self; + s_self = ref_self; + TCHECK(s_self == "self"); + + // --- Move assignment to self (same pointer) --- + lString8 s_selfmove {"selfmove"}; + s_selfmove = std::move(s_selfmove); + TCHECK(s_selfmove == "selfmove"); + + // --- Compare method --- + TCHECK(s5.compare(s6) == 0); + TCHECK(s5.compare("abc") == 0); + TCHECK(s5.compare("abd") < 0); + TCHECK(s5.compare("abb") > 0); + + // --- Length/size/capacity/empty consistency --- + lString8 s_len {"12345"}; + TCHECK(s_len.length() == 5); + TCHECK(s_len.size() == s_len.length()); + TCHECK(!s_len.empty()); + TCHECK(lString8{}.empty()); + TCHECK(lString8{}.length() == 0); + TCHECK(lString8{}.capacity() == 0); + + // --- Edge case: empty string construction --- + lString8 s_empty_str {""}; + TCHECK(s_empty_str.empty()); + TCHECK(s_empty_str.length() == 0); + + // --- Edge case: nullptr construction --- + lString8 s_null {nullptr}; + TCHECK(s_null.empty()); + TCHECK(s_null.length() == 0); + TCHECK(s_null.capacity() == 0); + + // --- Edge case: single char --- + lString8 s_one {"a"}; + TCHECK(s_one.length() == 1); + TCHECK(s_one[0] == 'a'); + + // --- Edge case: string with embedded null (count constructor) --- + lString8 s_embed {"ab\0cd", 5, 5}; + TCHECK(s_embed.length() == 5); + TCHECK(s_embed.capacity() >= 5); + + // --- Copy constructor --- + lString8 s_copy {"copyme"}; + lString8 s_copy2 {s_copy}; + TCHECK(s_copy2 == "copyme"); + TCHECK(s_copy2.length() == 6); + + // --- Move constructor --- + lString8 s_move_src {"moveme"}; + lString8 s_move_dst {std::move(s_move_src)}; + TCHECK(s_move_dst == "moveme"); + TCHECK(s_move_dst.length() == 6); + // s_move_src is now in moved-from state + + DUMP_ALLOC_STATS("after compare") + + // --- Reserve on empty string --- + lString8 s_rsv_empty; + TCHECK(s_rsv_empty.empty()); + TCHECK(s_rsv_empty.capacity() == 0); + s_rsv_empty.reserve(0); + TCHECK(s_rsv_empty.empty()); + TCHECK(s_rsv_empty.capacity() == 0); + + s_rsv_empty.reserve(100); + TCHECK(s_rsv_empty.empty()); + TCHECK(s_rsv_empty.length() == 0); + TCHECK(s_rsv_empty.capacity() >= 100); + + // --- Reserve on non-empty owned string --- + lString8 s_rsv_own {"hello"}; + size_t cap_before = s_rsv_own.capacity(); + s_rsv_own.reserve(3); + TCHECK(s_rsv_own.length() == 5); + TCHECK(s_rsv_own == "hello"); + TCHECK(s_rsv_own.capacity() == cap_before); // no shrink + + s_rsv_own.reserve(cap_before); + TCHECK(s_rsv_own.capacity() == cap_before); // exact match, no realloc + + s_rsv_own.reserve(cap_before + 50); + TCHECK(s_rsv_own.length() == 5); + TCHECK(s_rsv_own == "hello"); + TCHECK(s_rsv_own.capacity() >= cap_before + 50); + + // --- Reserve on shared string (forces copy) --- + lString8 s_rsv_src {"shared"}; + lString8 s_rsv_copy = s_rsv_src; + TCHECK(s_rsv_src.length() == 6); + TCHECK(s_rsv_copy.length() == 6); + s_rsv_copy.reserve(100); + TCHECK(s_rsv_copy.capacity() >= 100); + TCHECK(s_rsv_copy.length() == 6); + TCHECK(s_rsv_copy == "shared"); + // original unchanged + TCHECK(s_rsv_src.length() == 6); + TCHECK(s_rsv_src == "shared"); + + // --- Reserve on shared string with requested size < len --- + lString8 s_rsv_src2 {"longer string"}; + lString8 s_rsv_copy2 = s_rsv_src2; + s_rsv_copy2.reserve(1); + TCHECK(s_rsv_copy2.length() == 13); + TCHECK(s_rsv_copy2 == "longer string"); + TCHECK(s_rsv_copy2.capacity() >= 13); + + // --- Reserve(0) on non-empty string --- + lString8 s_rsv_nz {"test"}; + s_rsv_nz.reserve(0); + TCHECK(s_rsv_nz.length() == 4); + TCHECK(s_rsv_nz == "test"); + // capacity should not shrink + TCHECK(s_rsv_nz.capacity() >= 4); + + // --- c_str() on empty string --- + lString8 s_cstr_empty; + const char * p_empty = s_cstr_empty.c_str(); + TCHECK(p_empty != nullptr); + TCHECK(p_empty[0] == 0); + + // --- c_str() on non-empty string --- + lString8 s_cstr {"hello"}; + const char * p_cstr = s_cstr.c_str(); + TCHECK(p_cstr != nullptr); + TCHECK(strcmp(p_cstr, "hello") == 0); + TCHECK(p_cstr[5] == 0); + + // --- c_str() enforces null-termination --- + lString8 s_cstr2 {"abc"}; + // Manually corrupt the terminator (simulating a bug) + // c_str() should restore it + const char * p_cstr2 = s_cstr2.c_str(); + TCHECK(p_cstr2[3] == 0); + + // --- data() const on empty string --- + lString8 s_data_empty; + const char * p_dempty = s_data_empty.data(); + TCHECK(p_dempty != nullptr); + TCHECK(p_dempty[0] == 0); + TCHECK(p_dempty == s_data_empty.c_str()); + + // --- data() const on non-empty string --- + lString8 s_data {"world"}; + const char * p_data = s_data.data(); + TCHECK(p_data != nullptr); + TCHECK(strcmp(p_data, "world") == 0); + TCHECK(p_data == s_data.c_str()); + + // --- data() non-const on empty string --- + lString8 s_data_nc_empty; + char * p_nc_empty = s_data_nc_empty.data(); + TCHECK(p_nc_empty != nullptr); + TCHECK(p_nc_empty[0] == 0); + + // --- data() non-const on non-empty owned string --- + lString8 s_data_nc {"modify"}; + char * p_nc = s_data_nc.data(); + TCHECK(p_nc != nullptr); + TCHECK(strcmp(p_nc, "modify") == 0); + p_nc[0] = 'M'; + TCHECK(s_data_nc == "Modify"); + TCHECK(s_data_nc.length() == 6); + + // --- data() non-const on shared string (triggers lock/copy) --- + lString8 s_data_shared {"shared"}; + lString8 s_data_copy = s_data_shared; + TCHECK(s_data_shared == "shared"); + TCHECK(s_data_copy == "shared"); + char * p_shared = s_data_copy.data(); + TCHECK(p_shared != nullptr); + TCHECK(strcmp(p_shared, "shared") == 0); + p_shared[0] = 'S'; + TCHECK(s_data_copy == "Shared"); + // original must be unchanged + TCHECK(s_data_shared == "shared"); + + // --- data() non-const after multiple shares --- + lString8 s_multi {"multi"}; + lString8 s_m1 = s_multi; + lString8 s_m2 = s_multi; + lString8 s_m3 = s_multi; + char * p_multi = s_m2.data(); + TCHECK(strcmp(p_multi, "multi") == 0); + p_multi[0] = 'X'; + TCHECK(s_m2 == "Xulti"); + TCHECK(s_multi == "multi"); + TCHECK(s_m1 == "multi"); + TCHECK(s_m3 == "multi"); + + // --- lock() on null string (no-op) --- + lString8 s_lock_null; + s_lock_null.lock(100); + TCHECK(s_lock_null.empty()); + TCHECK(s_lock_null.capacity() == 0); + + // --- lock() on owned string (no-op when refCount==1) --- + lString8 s_lock_own {"owned"}; + size_t cap_lock_before = s_lock_own.capacity(); + s_lock_own.lock(50); + TCHECK(s_lock_own == "owned"); + TCHECK(s_lock_own.length() == 5); + TCHECK(s_lock_own.capacity() == cap_lock_before); // no realloc + + // --- lock() on shared string with newsize >= len --- + lString8 s_lock_src {"locktest"}; + lString8 s_lock_cpy = s_lock_src; + TCHECK(s_lock_src.length() == 8); + TCHECK(s_lock_cpy.length() == 8); + s_lock_cpy.lock(20); + TCHECK(s_lock_cpy == "locktest"); + TCHECK(s_lock_cpy.length() == 8); + TCHECK(s_lock_cpy.capacity() >= 20); + // original unchanged + TCHECK(s_lock_src == "locktest"); + TCHECK(s_lock_src.length() == 8); + + // --- lock() on shared string with newsize < len (ensures full copy) --- + lString8 s_lock_src2 {"longstring"}; + lString8 s_lock_cpy2 = s_lock_src2; + s_lock_cpy2.lock(3); + TCHECK(s_lock_cpy2 == "longstring"); + TCHECK(s_lock_cpy2.length() == 10); + TCHECK(s_lock_cpy2.capacity() >= 10); + // original unchanged + TCHECK(s_lock_src2 == "longstring"); + + // --- lock() on already-owned string with newsize < len (no-op) --- + lString8 s_lock_own2 {"short"}; + s_lock_own2.lock(2); + TCHECK(s_lock_own2 == "short"); + TCHECK(s_lock_own2.length() == 5); + + // --- data() non-const after lock() --- + lString8 s_lock_data {"before"}; + lString8 s_lock_data2 = s_lock_data; + s_lock_data2.lock(50); + char * p_ld = s_lock_data2.data(); + TCHECK(p_ld != nullptr); + TCHECK(strcmp(p_ld, "before") == 0); + p_ld[0] = 'A'; + TCHECK(s_lock_data2 == "Aefore"); + TCHECK(s_lock_data == "before"); + + // --- firstChar() / lastChar() on non-empty string --- + lString8 s_fl {"abc"}; + TCHECK(s_fl.firstChar() == 'a'); + TCHECK(s_fl.lastChar() == 'c'); + + // --- firstChar() / lastChar() on single-char string --- + lString8 s_one2 {"x"}; + TCHECK(s_one2.firstChar() == 'x'); + TCHECK(s_one2.lastChar() == 'x'); + + // --- firstChar() / lastChar() on empty string --- + lString8 s_empty_fl; + TCHECK(s_empty_fl.firstChar() == 0); + TCHECK(s_empty_fl.lastChar() == 0); + + // --- firstChar() / lastChar() on shared string --- + lString8 s_fl_shared {"shared"}; + lString8 s_fl_copy = s_fl_shared; + TCHECK(s_fl_shared.firstChar() == 's'); + TCHECK(s_fl_shared.lastChar() == 'd'); + TCHECK(s_fl_copy.firstChar() == 's'); + TCHECK(s_fl_copy.lastChar() == 'd'); + + // --- firstChar() / lastChar() after modification --- + lString8 s_fl_mod {"hello"}; + char * p_fl = s_fl_mod.data(); + p_fl[0] = 'H'; + p_fl[4] = 'O'; + TCHECK(s_fl_mod.firstChar() == 'H'); + TCHECK(s_fl_mod.lastChar() == 'O'); + + // --- firstChar() / lastChar() with embedded null --- + lString8 s_fl_null {"ab\0cd", 5, 5}; + TCHECK(s_fl_null.firstChar() == 'a'); + TCHECK(s_fl_null.lastChar() == 'd'); + TCHECK(s_fl_null.length() == 5); + + // --- Fragment constructor --- + lString8 s_frag_src {"hello world"}; + lString8 s_frag {s_frag_src, 6, 5}; + TCHECK(s_frag == "world"); + TCHECK(s_frag.length() == 5); + TCHECK(s_frag.capacity() >= 5); + + // --- Fragment constructor: offset beyond length --- + lString8 s_frag_off {s_frag_src, 100, 5}; + TCHECK(s_frag_off.empty()); + TCHECK(s_frag_off.length() == 0); + + // --- Fragment constructor: count exceeds remaining --- + lString8 s_frag_cnt {s_frag_src, 6, 100}; + TCHECK(s_frag_cnt == "world"); + TCHECK(s_frag_cnt.length() == 5); + + // --- Fragment constructor: empty source --- + lString8 s_frag_empty_src; + lString8 s_frag_empty {s_frag_empty_src, 0, 5}; + TCHECK(s_frag_empty.empty()); + + // --- Fragment constructor: zero count --- + lString8 s_frag_zero {s_frag_src, 0, 0}; + TCHECK(s_frag_zero.empty()); + + // --- Fragment assignment --- + lString8 s_fassign_src {"abcdef"}; + lString8 s_fassign {"initial"}; + s_fassign.assign(s_fassign_src, 2, 3); + TCHECK(s_fassign == "cde"); + TCHECK(s_fassign.length() == 3); + + // --- Fragment assignment: self-assignment --- + lString8 s_fassign_self {"selfassign"}; + s_fassign_self.assign(s_fassign_self, 4, 6); + TCHECK(s_fassign_self == "assign"); + + // --- Fragment assignment: offset beyond length --- + lString8 s_fassign_off {"short"}; + s_fassign_off.assign(s_fassign_off, 100, 5); + TCHECK(s_fassign_off.empty()); + + // --- Fragment assignment: count exceeds remaining --- + lString8 s_fassign_cnt {"hello"}; + s_fassign_cnt.assign(s_fassign_cnt, 2, 100); + TCHECK(s_fassign_cnt == "llo"); + TCHECK(s_fassign_cnt.length() == 3); + + // --- Fragment assignment: empty source --- + lString8 s_fassign_empty_src; + lString8 s_fassign_empty {"notempty"}; + s_fassign_empty.assign(s_fassign_empty_src, 0, 5); + TCHECK(s_fassign_empty.empty()); + + // --- modify() on empty string --- + lString8 s_mod_empty; + char * p_mod_empty = s_mod_empty.modify(); + TCHECK(p_mod_empty != nullptr); + p_mod_empty[0] = 'H'; + p_mod_empty[1] = 'i'; + p_mod_empty[2] = 0; + TCHECK(s_mod_empty.length() == 0); // modify doesn't change length + TCHECK(s_mod_empty.capacity() >= 8); + + // --- modify() on owned string --- + lString8 s_mod_own {"owned"}; + char * p_mod_own = s_mod_own.modify(); + TCHECK(strcmp(p_mod_own, "owned") == 0); + p_mod_own[0] = 'O'; + TCHECK(s_mod_own == "Owned"); + + // --- modify() on shared string (triggers copy) --- + lString8 s_mod_shared {"shared"}; + lString8 s_mod_copy = s_mod_shared; + char * p_mod_shared = s_mod_copy.modify(); + TCHECK(strcmp(p_mod_shared, "shared") == 0); + p_mod_shared[0] = 'S'; + TCHECK(s_mod_copy == "Shared"); + TCHECK(s_mod_shared == "shared"); // original unchanged + + // --- modify() after modification --- + lString8 s_mod_chain {"chain"}; + char * p_mod1 = s_mod_chain.modify(); + p_mod1[0] = 'C'; + char * p_mod2 = s_mod_chain.modify(); + p_mod2[1] = 'H'; + TCHECK(s_mod_chain == "CHain"); + + // --- erase() from middle of owned string --- + lString8 s_erase_mid {"hello world"}; + s_erase_mid.erase(5, 6); + TCHECK(s_erase_mid == "hello"); + TCHECK(s_erase_mid.length() == 5); + + // --- erase() from beginning of owned string --- + lString8 s_erase_beg {"hello world"}; + s_erase_beg.erase(0, 6); + TCHECK(s_erase_beg == "world"); + TCHECK(s_erase_beg.length() == 5); + + // --- erase() from end of owned string --- + lString8 s_erase_end {"hello world"}; + s_erase_end.erase(5, 100); + TCHECK(s_erase_end == "hello"); + TCHECK(s_erase_end.length() == 5); + + // --- erase() entire string --- + lString8 s_erase_all {"abc"}; + s_erase_all.erase(0, 3); + TCHECK(s_erase_all.empty()); + TCHECK(s_erase_all.length() == 0); + + // --- erase() with zero count (no-op) --- + lString8 s_erase_zero {"nochange"}; + s_erase_zero.erase(2, 0); + TCHECK(s_erase_zero == "nochange"); + + // --- erase() with offset beyond length (no-op) --- + lString8 s_erase_off {"short"}; + s_erase_off.erase(100, 5); + TCHECK(s_erase_off == "short"); + + // --- erase() on empty string (no-op) --- + lString8 s_erase_empty; + s_erase_empty.erase(0, 5); + TCHECK(s_erase_empty.empty()); + + // --- erase() on shared string (triggers COW copy) --- + lString8 s_erase_shared {"abcdef"}; + lString8 s_erase_copy = s_erase_shared; + s_erase_copy.erase(2, 2); + TCHECK(s_erase_copy == "abef"); + TCHECK(s_erase_copy.length() == 4); + // original unchanged + TCHECK(s_erase_shared == "abcdef"); + TCHECK(s_erase_shared.length() == 6); + + // --- erase() single char from middle --- + lString8 s_erase_one {"abcde"}; + s_erase_one.erase(2, 1); + TCHECK(s_erase_one == "abde"); + + // --- erase() with overlapping tail (memmove test) --- + lString8 s_erase_overlap {"0123456789"}; + s_erase_overlap.erase(0, 5); + TCHECK(s_erase_overlap == "56789"); + + // --- erase() on shared string, erasing from beginning --- + lString8 s_erase_sh_beg {"hello world"}; + lString8 s_erase_sh_beg2 = s_erase_sh_beg; + s_erase_sh_beg2.erase(0, 6); + TCHECK(s_erase_sh_beg2 == "world"); + TCHECK(s_erase_sh_beg == "hello world"); + + // --- erase() on shared string, erasing from end --- + lString8 s_erase_sh_end {"hello world"}; + lString8 s_erase_sh_end2 = s_erase_sh_end; + s_erase_sh_end2.erase(5, 6); + TCHECK(s_erase_sh_end2 == "hello"); + TCHECK(s_erase_sh_end == "hello world"); + + // --- erase() on shared string, erasing entire content --- + lString8 s_erase_sh_all {"abc"}; + lString8 s_erase_sh_all2 = s_erase_sh_all; + s_erase_sh_all2.erase(0, 3); + TCHECK(s_erase_sh_all2.empty()); + TCHECK(s_erase_sh_all == "abc"); + + // --- append(const char*) on empty string --- + lString8 s_app_empty; + s_app_empty.append("hello"); + TCHECK(s_app_empty == "hello"); + TCHECK(s_app_empty.length() == 5); + + // --- append(const char*) on non-empty owned string --- + lString8 s_app_own {"hello"}; + s_app_own.append(" world"); + TCHECK(s_app_own == "hello world"); + TCHECK(s_app_own.length() == 11); + + // --- append(const char*) with nullptr --- + lString8 s_app_null {"test"}; + s_app_null.append(nullptr); + TCHECK(s_app_null == "test"); + + // --- append(const char*) with empty string --- + lString8 s_app_emptystr {"test"}; + s_app_emptystr.append(""); + TCHECK(s_app_emptystr == "test"); + + // --- append(const char*, count) on empty string --- + lString8 s_app_fc_empty; + s_app_fc_empty.append("hello world", 5); + TCHECK(s_app_fc_empty == "hello"); + TCHECK(s_app_fc_empty.length() == 5); + + // --- append(const char*, count) with zero count --- + lString8 s_app_fc_zero {"test"}; + s_app_fc_zero.append("ignored", 0); + TCHECK(s_app_fc_zero == "test"); + + // --- append(const char*, count) on shared string (COW) --- + lString8 s_app_fc_shared {"base"}; + lString8 s_app_fc_copy = s_app_fc_shared; + s_app_fc_copy.append(" extended", 9); + TCHECK(s_app_fc_copy == "base extended"); + TCHECK(s_app_fc_copy.length() == 13); + TCHECK(s_app_fc_shared == "base"); + + // --- append(const char*, count) when capacity is insufficient --- + lString8 s_app_fc_cap {"short"}; + s_app_fc_cap.reserve(5); // ensure small capacity + s_app_fc_cap.append(" much longer string", 19); + TCHECK(s_app_fc_cap == "short much longer string"); + TCHECK(s_app_fc_cap.length() == 24); + + // --- append(const char*) chaining --- + lString8 s_app_chain; + s_app_chain.append("a"); + s_app_chain.append("b"); + s_app_chain.append("c"); + TCHECK(s_app_chain == "abc"); + + // --- append(const char*, count) self-append --- + lString8 s_app_self {"ab"}; + s_app_self.append(s_app_self.c_str(), 2); + TCHECK(s_app_self == "abab"); + + // --- append(const string&) on empty string --- + lString8 s_app_s_empty; + lString8 s_app_s_src {"hello"}; + s_app_s_empty.append(s_app_s_src); + TCHECK(s_app_s_empty == "hello"); + + // --- append(const string&) on non-empty string --- + lString8 s_app_s_own {"hello "}; + s_app_s_own.append(s_app_s_src); + TCHECK(s_app_s_own == "hello hello"); + + // --- append(const string&) with empty source --- + lString8 s_app_s_empty_src; + lString8 s_app_s_nochange {"test"}; + s_app_s_nochange.append(s_app_s_empty_src); + TCHECK(s_app_s_nochange == "test"); + + // --- append(const string&) self-append --- + lString8 s_app_s_self {"ab"}; + s_app_s_self.append(s_app_s_self); + TCHECK(s_app_s_self == "abab"); + + // --- append(const string&) self-append multiple times --- + lString8 s_app_s_multi {"x"}; + s_app_s_multi.append(s_app_s_multi); + TCHECK(s_app_s_multi == "xx"); + s_app_s_multi.append(s_app_s_multi); + TCHECK(s_app_s_multi == "xxxx"); + + // --- append(const string&) with insufficient capacity --- + lString8 s_app_s_cap {"short"}; + s_app_s_cap.reserve(5); + lString8 s_app_s_long {" much longer string"}; + s_app_s_cap.append(s_app_s_long); + TCHECK(s_app_s_cap == "short much longer string"); + + // --- append(const string&, offset, count) normal --- + lString8 s_app_sf_src {"hello world"}; + lString8 s_app_sf {"start "}; + s_app_sf.append(s_app_sf_src, 6, 5); + TCHECK(s_app_sf == "start world"); + + // --- append(const string&, offset, count) self-append --- + lString8 s_app_sf_self {"abcd"}; + s_app_sf_self.append(s_app_sf_self, 0, 4); + TCHECK(s_app_sf_self == "abcdabcd"); + + // --- append(const string&, offset, count) self-append fragment --- + lString8 s_app_sf_self2 {"abcd"}; + s_app_sf_self2.append(s_app_sf_self2, 1, 2); + TCHECK(s_app_sf_self2 == "abcdbc"); + + // --- append(const string&, offset, count) count exceeds remaining --- + lString8 s_app_sf_clamp {"hello"}; + lString8 s_app_sf_clap {"start"}; + s_app_sf_clap.append(s_app_sf_clamp, 3, 100); + TCHECK(s_app_sf_clap == "startlo"); + + // --- append(const string&, offset, count) offset beyond length --- + lString8 s_app_sf_off {"hello"}; + lString8 s_app_sf_off2 {"start"}; + s_app_sf_off2.append(s_app_sf_off, 100, 5); + TCHECK(s_app_sf_off2 == "start"); + + // --- append(const string&, offset, count) empty source --- + lString8 s_app_sf_empty; + lString8 s_app_sf_empty2 {"test"}; + s_app_sf_empty2.append(s_app_sf_empty, 0, 5); + TCHECK(s_app_sf_empty2 == "test"); + + // --- append(count, char) on empty string --- + lString8 s_app_cc_empty; + s_app_cc_empty.append(5, 'x'); + TCHECK(s_app_cc_empty == "xxxxx"); + TCHECK(s_app_cc_empty.length() == 5); + + // --- append(count, char) on owned string with capacity --- + lString8 s_app_cc_cap {"ab"}; + s_app_cc_cap.reserve(10); + s_app_cc_cap.append(3, 'c'); + TCHECK(s_app_cc_cap == "abccc"); + TCHECK(s_app_cc_cap.length() == 5); + + // --- append(count, char) on owned string without capacity --- + lString8 s_app_cc_nocap {"ab"}; + s_app_cc_nocap.append(5, 'z'); + TCHECK(s_app_cc_nocap == "abzzzzz"); + TCHECK(s_app_cc_nocap.length() == 7); + + // --- append(count, char) on shared string --- + lString8 s_app_cc_shared {"base"}; + lString8 s_app_cc_copy = s_app_cc_shared; + s_app_cc_copy.append(3, '!'); + TCHECK(s_app_cc_copy == "base!!!"); + TCHECK(s_app_cc_shared == "base"); + + // --- append(count, char) with zero count --- + lString8 s_app_cc_zero {"test"}; + s_app_cc_zero.append(0, 'x'); + TCHECK(s_app_cc_zero == "test"); + + // --- append(count, char) with null char --- + lString8 s_app_cc_null {"ab"}; + s_app_cc_null.append(2, '\0'); + TCHECK(s_app_cc_null.length() == 4); + TCHECK(s_app_cc_null[2] == 0); + TCHECK(s_app_cc_null[3] == 0); + TCHECK(s_app_cc_null.c_str()[0] == 'a'); + + // --- insert(pos, char*, count) into empty string --- + lString8 s_ins_empty; + s_ins_empty.insert(0, "hello", 5); + TCHECK(s_ins_empty == "hello"); + TCHECK(s_ins_empty.length() == 5); + + // --- insert(pos, char*, count) at beginning --- + lString8 s_ins_beg {"world"}; + s_ins_beg.insert(0, "hello ", 6); + TCHECK(s_ins_beg == "hello world"); + + // --- insert(pos, char*, count) in middle --- + lString8 s_ins_mid {"helloworld"}; + s_ins_mid.insert(5, " ", 1); + TCHECK(s_ins_mid == "hello world"); + + // --- insert(pos, char*, count) at end --- + lString8 s_ins_end {"hello"}; + s_ins_end.insert(5, " world", 6); + TCHECK(s_ins_end == "hello world"); + + // --- insert(pos, char*, count) with pos beyond length --- + lString8 s_ins_off {"abc"}; + s_ins_off.insert(100, "xyz", 3); + TCHECK(s_ins_off == "abcxyz"); + + // --- insert(pos, char*, count) with zero count --- + lString8 s_ins_zero {"test"}; + s_ins_zero.insert(2, "ignored", 0); + TCHECK(s_ins_zero == "test"); + + // --- insert(pos, char*, count) on shared string --- + lString8 s_ins_shared {"base"}; + lString8 s_ins_copy = s_ins_shared; + s_ins_copy.insert(2, "XX", 2); + TCHECK(s_ins_copy == "baXXse"); + TCHECK(s_ins_shared == "base"); + + // --- insert(pos, char*, count) with insufficient capacity --- + lString8 s_ins_cap {"ab"}; + s_ins_cap.reserve(2); + s_ins_cap.insert(1, " much longer ", 13); + TCHECK(s_ins_cap == "a much longer b"); + TCHECK(s_ins_cap.length() == 15); + + // --- insert(pos, char*) --- + lString8 s_ins_cstr {"helloworld"}; + s_ins_cstr.insert(5, " "); + TCHECK(s_ins_cstr == "hello world"); + + // --- insert(pos, char*) with nullptr --- + lString8 s_ins_null {"test"}; + s_ins_null.insert(2, nullptr); + TCHECK(s_ins_null == "test"); + + // --- insert(pos, char*) with empty string --- + lString8 s_ins_emptystr {"test"}; + s_ins_emptystr.insert(2, ""); + TCHECK(s_ins_emptystr == "test"); + + // --- insert(pos, char*, count) self-insert prefix --- + lString8 s_ins_self {"abcd"}; + s_ins_self.insert(2, s_ins_self.c_str(), 2); + TCHECK(s_ins_self == "ababcd"); + + // --- insert(pos, string&) --- + lString8 s_ins_str {"helloworld"}; + lString8 s_ins_str_src {" "}; + s_ins_str.insert(5, s_ins_str_src); + TCHECK(s_ins_str == "hello world"); + + // --- insert(pos, string&) with empty source --- + lString8 s_ins_str_empty {"test"}; + lString8 s_ins_str_empty_src; + s_ins_str_empty.insert(2, s_ins_str_empty_src); + TCHECK(s_ins_str_empty == "test"); + + // --- insert(pos, string&) self-insert --- + lString8 s_ins_str_self {"ab"}; + s_ins_str_self.insert(1, s_ins_str_self); + TCHECK(s_ins_str_self == "aabb"); + + // --- insert(pos, count, char) on empty string --- + lString8 s_ins_cc_empty; + s_ins_cc_empty.insert(0, 5, 'x'); + TCHECK(s_ins_cc_empty == "xxxxx"); + + // --- insert(pos, count, char) in middle --- + lString8 s_ins_cc_mid {"ab"}; + s_ins_cc_mid.insert(1, 3, '-'); + TCHECK(s_ins_cc_mid == "a---b"); + + // --- insert(pos, count, char) at end --- + lString8 s_ins_cc_end {"ab"}; + s_ins_cc_end.insert(2, 3, '!'); + TCHECK(s_ins_cc_end == "ab!!!"); + + // --- insert(pos, count, char) pos beyond length --- + lString8 s_ins_cc_off {"ab"}; + s_ins_cc_off.insert(100, 2, 'z'); + TCHECK(s_ins_cc_off == "abzz"); + + // --- insert(pos, count, char) zero count --- + lString8 s_ins_cc_zero {"test"}; + s_ins_cc_zero.insert(2, 0, 'x'); + TCHECK(s_ins_cc_zero == "test"); + + // --- insert(pos, count, char) on shared string --- + lString8 s_ins_cc_shared {"base"}; + lString8 s_ins_cc_copy = s_ins_cc_shared; + s_ins_cc_copy.insert(2, 2, 'X'); + TCHECK(s_ins_cc_copy == "baXXse"); + TCHECK(s_ins_cc_shared == "base"); + + // --- insert(pos, count, char) with insufficient capacity --- + lString8 s_ins_cc_cap {"ab"}; + s_ins_cc_cap.reserve(2); + s_ins_cc_cap.insert(1, 5, '-'); + TCHECK(s_ins_cc_cap == "a-----b"); + + // --- pack() on string with excess capacity --- + lString8 s_pack {"hello"}; + s_pack.reserve(1000); + TCHECK(s_pack.capacity() >= 1000); + s_pack.pack(); + TCHECK(s_pack == "hello"); + TCHECK(s_pack.length() == 5); + TCHECK(s_pack.capacity() < 1000); + + // --- pack() on string with no excess capacity --- + lString8 s_pack_exact {"exact"}; + size_t cap_before2 = s_pack_exact.capacity(); + s_pack_exact.pack(); + TCHECK(s_pack_exact == "exact"); + TCHECK(s_pack_exact.capacity() == cap_before2); + + // --- pack() on empty string --- + lString8 s_pack_empty; + s_pack_empty.pack(); + TCHECK(s_pack_empty.empty()); + + // --- pack() on shared string --- + lString8 s_pack_shared {"shared"}; + lString8 s_pack_copy = s_pack_shared; + s_pack_copy.reserve(100); + s_pack_copy.pack(); + TCHECK(s_pack_copy == "shared"); + TCHECK(s_pack_copy.capacity() < 100); + TCHECK(s_pack_shared == "shared"); + + // --- replace(pos, n, char*, count) normal --- + lString8 s_rep1 {"hello world"}; + s_rep1.replace(6, 5, "earth", 5); + TCHECK(s_rep1 == "hello earth"); + + // --- replace(pos, n, char*, count) longer replacement --- + lString8 s_rep2 {"abc"}; + s_rep2.replace(1, 1, "XYZ", 3); + TCHECK(s_rep2 == "aXYZc"); + TCHECK(s_rep2.length() == 5); + + // --- replace(pos, n, char*, count) shorter replacement --- + lString8 s_rep3 {"hello world"}; + s_rep3.replace(6, 5, "there", 5); + TCHECK(s_rep3 == "hello there"); + + // --- replace(pos, n, char*, count) n=0 (insert) --- + lString8 s_rep4 {"ab"}; + s_rep4.replace(1, 0, "X", 1); + TCHECK(s_rep4 == "aXb"); + + // --- replace(pos, n, char*, count) pos beyond length --- + lString8 s_rep5 {"ab"}; + s_rep5.replace(100, 5, "X", 1); + TCHECK(s_rep5 == "abX"); + + // --- replace(pos, n, char*, count) n exceeds remaining --- + lString8 s_rep6 {"hello"}; + s_rep6.replace(3, 100, "X", 1); + TCHECK(s_rep6 == "helX"); + + // --- replace(pos, n, char*, count) on shared string --- + lString8 s_rep7 {"shared"}; + lString8 s_rep7_copy = s_rep7; + s_rep7_copy.replace(0, 6, "new", 3); + TCHECK(s_rep7_copy == "new"); + TCHECK(s_rep7 == "shared"); + + // --- replace(pos, n, char*) --- + lString8 s_rep8 {"hello world"}; + s_rep8.replace(6, 5, "earth"); + TCHECK(s_rep8 == "hello earth"); + + // --- replace(pos, n, char*) with nullptr --- + lString8 s_rep9 {"test"}; + s_rep9.replace(1, 2, nullptr); + TCHECK(s_rep9 == "test"); + + // --- replace(pos, n, string&) --- + lString8 s_rep10 {"hello world"}; + lString8 s_rep10_src {"earth"}; + s_rep10.replace(6, 5, s_rep10_src); + TCHECK(s_rep10 == "hello earth"); + + // --- replace(pos, n, string&) self-replace --- + lString8 s_rep11 {"abcde"}; + s_rep11.replace(1, 3, s_rep11); + TCHECK(s_rep11 == "aabcdee"); + + // --- replace(pos, n, string&, offset, count) --- + lString8 s_rep12 {"hello world"}; + lString8 s_rep12_src {"abcdefgh"}; + s_rep12.replace(6, 5, s_rep12_src, 2, 3); + TCHECK(s_rep12 == "hello cde"); + + // --- replace(pos, n, string&, offset, count) offset beyond --- + lString8 s_rep13 {"test"}; + lString8 s_rep13_src {"abc"}; + s_rep13.replace(1, 2, s_rep13_src, 100, 5); + TCHECK(s_rep13 == "test"); + + // --- replace(pos, n, count, char) --- + lString8 s_rep14 {"hello world"}; + s_rep14.replace(6, 5, 5, 'X'); + TCHECK(s_rep14 == "hello XXXXX"); + + // --- replace(pos, n, count, char) longer --- + lString8 s_rep15 {"abc"}; + s_rep15.replace(1, 1, 3, 'X'); + TCHECK(s_rep15 == "aXXXc"); + + // --- replace(char, char) --- + lString8 s_rep16 {"hello world"}; + s_rep16.replace('l', 'L'); + TCHECK(s_rep16 == "heLLo worLd"); + + // --- replace(char, char) no match --- + lString8 s_rep17 {"hello"}; + s_rep17.replace('z', 'Z'); + TCHECK(s_rep17 == "hello"); + + // --- replace(char, char) on shared string --- + lString8 s_rep18 {"abcabc"}; + lString8 s_rep18_copy = s_rep18; + s_rep18_copy.replace('a', 'X'); + TCHECK(s_rep18_copy == "XbcXbc"); + TCHECK(s_rep18 == "abcabc"); + + // --- replace(char, char) empty string --- + lString8 s_rep19; + s_rep19.replace('a', 'b'); + TCHECK(s_rep19.empty()); + + // --- compare(pos, n, string&) --- + lString8 s_cmp1 {"hello world"}; + lString8 s_cmp1_src {"world"}; + TCHECK(s_cmp1.compare(6, 5, s_cmp1_src) == 0); + TCHECK(s_cmp1.compare(6, 5, lString8{"hello"}) > 0); // "world" > "hello" + TCHECK(s_cmp1.compare(6, 5, lString8{"zebra"}) < 0); // "world" < "zebra" + + // --- compare(pos, n, string&) n exceeds bounds --- + TCHECK(s_cmp1.compare(6, 100, s_cmp1_src) == 0); + + // --- compare(pos, n, string&) pos beyond length --- + TCHECK(s_cmp1.compare(100, 5, lString8{"x"}) < 0); // "" < "x" + + // --- compare(pos, n, string&, pos2, n2) --- + lString8 s_cmp2 {"abcdef"}; + lString8 s_cmp2_src {"xyzbcdefg"}; + TCHECK(s_cmp2.compare(1, 4, s_cmp2_src, 3, 4) == 0); // "bcde" == "bcde" + TCHECK(s_cmp2.compare(1, 4, s_cmp2_src, 3, 3) > 0); // "bcde" > "bcd" + TCHECK(s_cmp2.compare(1, 3, s_cmp2_src, 3, 4) < 0); // "bcd" < "bcde" + + // --- compare(pos, n, string&, pos2, n2) pos2 beyond --- + TCHECK(s_cmp2.compare(1, 4, s_cmp2_src, 100, 5) > 0); // "bcde" > "" + + // --- compare(pos, n, char*, n2) --- + TCHECK(s_cmp2.compare(1, 4, "bcde", 4) == 0); + TCHECK(s_cmp2.compare(1, 4, "bcd", 3) > 0); + TCHECK(s_cmp2.compare(1, 3, "bcde", 4) < 0); + + // --- compare(pos, n, char*, n2) nullptr --- + TCHECK(s_cmp2.compare(1, 4, nullptr, 0) > 0); // "bcde" > "" + + // --- compare(pos, n, char*) --- + TCHECK(s_cmp2.compare(1, 4, "bcde") == 0); + TCHECK(s_cmp2.compare(1, 4, nullptr) > 0); + TCHECK(s_cmp2.compare(1, 4, "") > 0); + + // --- compare(pos, n, char*) n exceeds bounds --- + TCHECK(s_cmp2.compare(1, 100, "bcde") > 0); // "bcdef" > "bcde" + + // --- compare(pos, n, char*) pos beyond length --- + TCHECK(s_cmp2.compare(100, 5, "x") < 0); + + // --- compare(const char*) with nullptr --- + lString8 s_cmp3 {"test"}; + TCHECK(s_cmp3.compare(nullptr) > 0); + + // --- compare(const char*) with empty --- + TCHECK(s_cmp3.compare("") > 0); + + // --- compare(const char*) equal --- + TCHECK(s_cmp3.compare("test") == 0); + + // --- compare(const char*) less/greater --- + TCHECK(s_cmp3.compare("zebra") < 0); + TCHECK(s_cmp3.compare("abc") > 0); + + // --- pos(char) --- + lString8 s_pos1 {"hello world"}; + TCHECK(s_pos1.pos('o') == 4); + TCHECK(s_pos1.pos('z') == lString8::npos); + TCHECK(s_pos1.pos('h') == 0); + TCHECK(s_pos1.pos('d') == 10); + + // --- pos(char) on empty string --- + lString8 s_pos_empty; + TCHECK(s_pos_empty.pos('a') == lString8::npos); + + // --- pos(char, start) --- + TCHECK(s_pos1.pos('o', 5) == 7); + TCHECK(s_pos1.pos('o', 8) == lString8::npos); + TCHECK(s_pos1.pos('o', 4) == 4); + TCHECK(s_pos1.pos('o', 100) == lString8::npos); + + // --- pos(string) --- + lString8 s_pos2 {"hello world hello"}; + lString8 s_pos2_pat {"hello"}; + TCHECK(s_pos2.pos(s_pos2_pat) == 0); + TCHECK(s_pos2.pos(lString8{"world"}) == 6); + TCHECK(s_pos2.pos(lString8{"xyz"}) == lString8::npos); + + // --- pos(string) empty pattern --- + TCHECK(s_pos2.pos(lString8{}) == lString8::npos); + + // --- pos(string) empty source --- + lString8 s_pos_empty_src; + TCHECK(s_pos_empty_src.pos(s_pos2_pat) == lString8::npos); + + // --- pos(string, start) --- + TCHECK(s_pos2.pos(s_pos2_pat, 1) == 12); + TCHECK(s_pos2.pos(s_pos2_pat, 13) == lString8::npos); + TCHECK(s_pos2.pos(s_pos2_pat, 12) == 12); + + // --- pos(string) at exact end --- + lString8 s_pos_end {"abcde"}; + TCHECK(s_pos_end.pos(lString8{"e"}) == 4); + TCHECK(s_pos_end.pos(lString8{"de"}) == 3); + TCHECK(s_pos_end.pos(lString8{"cde"}) == 2); + TCHECK(s_pos_end.pos(lString8{"abcde"}) == 0); + + // --- pos(c-str) --- + TCHECK(s_pos1.pos("world") == 6); + TCHECK(s_pos1.pos("xyz") == lString8::npos); + TCHECK(s_pos1.pos(nullptr) == lString8::npos); + TCHECK(s_pos1.pos("") == lString8::npos); + + // --- pos(c-str, start) --- + TCHECK(s_pos1.pos("world", 0) == 6); + TCHECK(s_pos1.pos("world", 7) == lString8::npos); + TCHECK(s_pos1.pos("world", 6) == 6); + TCHECK(s_pos1.pos("world", 100) == lString8::npos); + + // --- pos(c-str) at exact end --- + TCHECK(s_pos_end.pos("e") == 4); + TCHECK(s_pos_end.pos("de") == 3); + TCHECK(s_pos_end.pos("cde") == 2); + TCHECK(s_pos_end.pos("abcde") == 0); + + // --- rpos(c-str) --- + lString8 s_rpos1 {"hello world hello"}; + TCHECK(s_rpos1.rpos("hello") == 12); + TCHECK(s_rpos1.rpos("world") == 6); + TCHECK(s_rpos1.rpos("xyz") == lString8::npos); + + // --- rpos(c-str) single char --- + TCHECK(s_rpos1.rpos("o") == 16); + TCHECK(s_rpos1.rpos("h") == 12); + TCHECK(s_rpos1.rpos("z") == lString8::npos); + + // --- rpos(c-str) at beginning --- + TCHECK(s_rpos1.rpos("hello world") == 0); + + // --- rpos(c-str) entire string --- + TCHECK(s_rpos1.rpos("hello world hello") == 0); + + // --- rpos(c-str) longer than string --- + TCHECK(s_rpos1.rpos("hello world hello world") == lString8::npos); + + // --- rpos(c-str) nullptr --- + TCHECK(s_rpos1.rpos(nullptr) == lString8::npos); + + // --- rpos(c-str) empty --- + TCHECK(s_rpos1.rpos("") == lString8::npos); + + // --- rpos(c-str) empty string --- + lString8 s_rpos_empty; + TCHECK(s_rpos_empty.rpos("test") == lString8::npos); + + // --- startsWith(c-str) --- + lString8 s_sw1 {"hello world"}; + TCHECK(s_sw1.startsWith("hello")); + TCHECK(s_sw1.startsWith("hello world")); + TCHECK(!s_sw1.startsWith("world")); + TCHECK(!s_sw1.startsWith("hello world!")); + TCHECK(!s_sw1.startsWith("")); + TCHECK(!s_sw1.startsWith(nullptr)); + + // --- startsWith(c-str, count) --- + TCHECK(s_sw1.startsWith("hel", 3)); + TCHECK(s_sw1.startsWith("hello", 5)); + TCHECK(!s_sw1.startsWith("help", 4)); + TCHECK(!s_sw1.startsWith("hello world!", 12)); + + // --- startsWith(string) --- + lString8 s_sw2 {"hello"}; + TCHECK(s_sw1.startsWith(s_sw2)); + TCHECK(!s_sw2.startsWith(s_sw1)); + + // --- startsWith(string) empty source --- + lString8 s_sw_empty; + TCHECK(!s_sw_empty.startsWith(s_sw2)); + TCHECK(!s_sw_empty.startsWith("hello")); + + // --- startsWith(string) empty pattern --- + lString8 s_sw_empty_pat; + TCHECK(!s_sw1.startsWith(s_sw_empty_pat)); + + // --- endsWith(c-str) --- + lString8 s_ew1 {"hello world"}; + TCHECK(s_ew1.endsWith("world")); + TCHECK(s_ew1.endsWith("hello world")); + TCHECK(!s_ew1.endsWith("hello")); + TCHECK(!s_ew1.endsWith("hello world!")); + TCHECK(!s_ew1.endsWith("")); + TCHECK(!s_ew1.endsWith(nullptr)); + + // --- endsWith(c-str, count) --- + TCHECK(s_ew1.endsWith("orld", 4)); + TCHECK(s_ew1.endsWith("world", 5)); + TCHECK(!s_ew1.endsWith("word", 4)); + TCHECK(!s_ew1.endsWith("hello world!", 12)); + + // --- endsWith(string) --- + lString8 s_ew2 {"world"}; + TCHECK(s_ew1.endsWith(s_ew2)); + TCHECK(!s_ew2.endsWith(s_ew1)); + + // --- endsWith(string) empty source --- + lString8 s_ew_empty; + TCHECK(!s_ew_empty.endsWith(s_ew2)); + TCHECK(!s_ew_empty.endsWith("world")); + + // --- endsWith(string) empty pattern --- + lString8 s_ew_empty_pat; + TCHECK(!s_ew1.endsWith(s_ew_empty_pat)); + + // --- substr(pos, count) --- + lString8 s_sub1 {"hello world"}; + TCHECK(s_sub1.substr(0, 5) == "hello"); + TCHECK(s_sub1.substr(6, 5) == "world"); + TCHECK(s_sub1.substr(0, 11) == "hello world"); + TCHECK(s_sub1.substr(5, 1) == " "); + TCHECK(s_sub1.substr(10, 1) == "d"); + + // --- substr(pos, count) count exceeds remaining --- + TCHECK(s_sub1.substr(6, 100) == "world"); + + // --- substr(pos, count) pos beyond length --- + TCHECK(s_sub1.substr(100, 5) == ""); + + // --- substr(pos, count) pos at end --- + TCHECK(s_sub1.substr(11, 5) == ""); + + // --- substr(pos) default count=npos --- + TCHECK(s_sub1.substr(6) == "world"); + TCHECK(s_sub1.substr(0) == "hello world"); + + // --- substr(pos, count) empty string --- + lString8 s_sub_empty; + TCHECK(s_sub_empty.substr(0, 5) == ""); + TCHECK(s_sub_empty.substr(0) == ""); + + // --- appendDecimal --- + lString8 s_ad; + s_ad.appendDecimal(0); + TCHECK(s_ad == "0"); + s_ad.appendDecimal(42); + TCHECK(s_ad == "042"); + s_ad.appendDecimal(-123); + TCHECK(s_ad == "042-123"); + s_ad.appendDecimal(999999999999999999LL); + TCHECK(s_ad == "042-123999999999999999999"); + + // --- appendHex --- + lString8 s_ah; + s_ah.appendHex(0); + TCHECK(s_ah == "0"); + s_ah.appendHex(0xFF); + TCHECK(s_ah == "0ff"); + s_ah.appendHex(0xABCDEF); + TCHECK(s_ah == "0ffabcdef"); + + // --- operator << (char) --- + lString8 s_sl; + s_sl << 'A'; + TCHECK(s_sl == "A"); + s_sl << 'B'; + TCHECK(s_sl == "AB"); + + // --- operator << (c-str) --- + lString8 s_sl2; + s_sl2 << "hello"; + TCHECK(s_sl2 == "hello"); + s_sl2 << " world"; + TCHECK(s_sl2 == "hello world"); + + // --- operator << (string) --- + lString8 s_sl3; + s_sl3 << lString8{"test"}; + TCHECK(s_sl3 == "test"); + + // --- operator << (fmt::decimal) --- + lString8 s_sl4; + s_sl4 << fmt::decimal(42); + TCHECK(s_sl4 == "42"); + s_sl4 << fmt::decimal(-99); + TCHECK(s_sl4 == "42-99"); + + // --- operator << (fmt::hex) --- + lString8 s_sl5; + s_sl5 << fmt::hex(255); + TCHECK(s_sl5 == "ff"); + s_sl5 << fmt::hex(0xABC); + TCHECK(s_sl5 == "ffabc"); + + // --- uppercase() --- + lString8 s_upper {"Hello World"}; + s_upper.uppercase(); + TCHECK(s_upper == "HELLO WORLD"); + + // --- uppercase() on shared string --- + lString8 s_upper_shared {"shared"}; + lString8 s_upper_copy = s_upper_shared; + s_upper_copy.uppercase(); + TCHECK(s_upper_copy == "SHARED"); + TCHECK(s_upper_shared == "shared"); + + // --- uppercase() on empty string --- + lString8 s_upper_empty; + s_upper_empty.uppercase(); + TCHECK(s_upper_empty.empty()); + + // --- uppercase() already uppercase --- + lString8 s_upper_done {"ALREADY"}; + s_upper_done.uppercase(); + TCHECK(s_upper_done == "ALREADY"); + + // --- lowercase() --- + lString8 s_lower {"Hello World"}; + s_lower.lowercase(); + TCHECK(s_lower == "hello world"); + + // --- lowercase() on shared string --- + lString8 s_lower_shared {"SHARED"}; + lString8 s_lower_copy = s_lower_shared; + s_lower_copy.lowercase(); + TCHECK(s_lower_copy == "shared"); + TCHECK(s_lower_shared == "SHARED"); + + // --- lowercase() on empty string --- + lString8 s_lower_empty; + s_lower_empty.lowercase(); + TCHECK(s_lower_empty.empty()); + + // --- lowercase() already lowercase --- + lString8 s_lower_done {"already"}; + s_lower_done.lowercase(); + TCHECK(s_lower_done == "already"); + + // --- operator += (char) --- + lString8 s_plus1; + s_plus1 += 'A'; + TCHECK(s_plus1 == "A"); + s_plus1 += 'B'; + TCHECK(s_plus1 == "AB"); + + // --- operator += (c-str) --- + lString8 s_plus2; + s_plus2 += "hello"; + TCHECK(s_plus2 == "hello"); + s_plus2 += " world"; + TCHECK(s_plus2 == "hello world"); + + // --- operator += (string) --- + lString8 s_plus3; + s_plus3 += lString8{"test"}; + TCHECK(s_plus3 == "test"); + s_plus3 += s_plus3; + TCHECK(s_plus3 == "testtest"); + + // --- operator += (fmt::decimal) --- + lString8 s_plus4; + s_plus4 += fmt::decimal(42); + TCHECK(s_plus4 == "42"); + s_plus4 += fmt::decimal(-99); + TCHECK(s_plus4 == "42-99"); + + // --- operator += (fmt::hex) --- + lString8 s_plus5; + s_plus5 += fmt::hex(255); + TCHECK(s_plus5 == "ff"); + s_plus5 += fmt::hex(0xABC); + TCHECK(s_plus5 == "ffabc"); + + // --- string_wr operator += --- + lString8 s_wr_plus {"base"}; + auto& s_wr = s_wr_plus.writableRef(); + s_wr += " extended"; + TCHECK(s_wr_plus == "base extended"); + s_wr += '!'; + TCHECK(s_wr_plus == "base extended!"); + + // --- string_wr operator << --- + lString8 s_wr_sl {"start"}; + auto& s_wr2 = s_wr_sl.writableRef(); + s_wr2 << " middle"; + TCHECK(s_wr_sl == "start middle"); + s_wr2 << fmt::decimal(123); + TCHECK(s_wr_sl == "start middle123"); + + // --- atoi() --- + lString8 s_atoi1 {"12345"}; + TCHECK(s_atoi1.atoi() == 12345); + lString8 s_atoi2 {"-999"}; + TCHECK(s_atoi2.atoi() == -999); + lString8 s_atoi3 {"0"}; + TCHECK(s_atoi3.atoi() == 0); + lString8 s_atoi4 {" +42"}; + TCHECK(s_atoi4.atoi() == 42); + lString8 s_atoi5 {}; + TCHECK(s_atoi5.atoi() == 0); + + // --- atoi(int&) --- + { + int n; + lString8 s_ai {"6789"}; + TCHECK(s_ai.atoi(n) && n == 6789); + lString8 s_ai2 {"-123"}; + TCHECK(s_ai2.atoi(n) && n == -123); + lString8 s_ai3 {"0x1A"}; + TCHECK(s_ai3.atoi(n) && n == 0x1A); + lString8 s_ai4 {" 0xFF"}; + TCHECK(s_ai4.atoi(n) && n == 255); + lString8 s_ai5 {"abc"}; + TCHECK(!s_ai5.atoi(n)); + lString8 s_ai6 {" "}; + TCHECK(!s_ai6.atoi(n)); + lString8 s_ai7 {}; + TCHECK(!s_ai7.atoi(n)); + lString8 s_ai8 {"123 abc"}; + TCHECK(s_ai8.atoi(n) && n == 123); + lString8 s_ai9 {" 123 "}; + TCHECK(s_ai9.atoi(n) && n == 123); + lString8 s_ai10 {"0"}; + TCHECK(s_ai10.atoi(n) && n == 0); + } + + // --- atoi(lInt64&) --- + { + lInt64 n; + lString8 s_ai64 {"1234567890123"}; + TCHECK(s_ai64.atoi(n) && n == 1234567890123LL); + lString8 s_ai64_2 {"-999"}; + TCHECK(s_ai64_2.atoi(n) && n == -999); + lString8 s_ai64_3 {"0xABADCAFE"}; + TCHECK(s_ai64_3.atoi(n) && n == 0xABADCAFELL); + lString8 s_ai64_4 {" 0xFF"}; + TCHECK(s_ai64_4.atoi(n) && n == 255); + lString8 s_ai64_5 {"abc"}; + TCHECK(!s_ai64_5.atoi(n)); + } + + // --- atoi64() --- + lString8 s_atoi64 {"1234567890123"}; + TCHECK(s_atoi64.atoi64() == 1234567890123LL); + lString8 s_atoi64_2 {"-999"}; + TCHECK(s_atoi64_2.atoi64() == -999); + lString8 s_atoi64_3 {"0"}; + TCHECK(s_atoi64_3.atoi64() == 0); + lString8 s_atoi64_4 {}; + TCHECK(s_atoi64_4.atoi64() == 0); + + // --- itoa(int) static --- + TCHECK(lString8::itoa(42) == "42"); + TCHECK(lString8::itoa(-42) == "-42"); + TCHECK(lString8::itoa(0) == "0"); + TCHECK(lString8::itoa(1) == "1"); + TCHECK(lString8::itoa(-1) == "-1"); + TCHECK(lString8::itoa(2147483647) == "2147483647"); + TCHECK(lString8::itoa(-2147483647) == "-2147483647"); + + // --- itoa(unsigned) static --- + TCHECK(lString8::itoa(0U) == "0"); + TCHECK(lString8::itoa(1U) == "1"); + TCHECK(lString8::itoa(4294967295U) == "4294967295"); + + // --- itoa(lInt64) static --- + TCHECK(lString8::itoa(0LL) == "0"); + TCHECK(lString8::itoa(-123LL) == "-123"); + TCHECK(lString8::itoa(9223372036854775807LL) == "9223372036854775807"); + + // --- getHash() empty --- + lString8 s_gh_empty; + TCHECK(s_gh_empty.getHash() == 0); + + // --- getHash() non-empty --- + lString8 s_gh1 {"hello"}; + lString8 s_gh2 {"hello"}; + TCHECK(s_gh1.getHash() == s_gh2.getHash()); + + // --- getHash() different --- + lString8 s_gh3 {"world"}; + TCHECK(s_gh1.getHash() != s_gh3.getHash()); + + // --- getHash() consistent --- + size_t h1 = s_gh1.getHash(); + size_t h2 = s_gh1.getHash(); + TCHECK(h1 == h2); + + // --- trim() leading spaces --- + lString8 s_tr_l {" hello"}; + s_tr_l.trim(); + TCHECK(s_tr_l == "hello"); + + // --- trim() trailing spaces --- + lString8 s_tr_t {"hello "}; + s_tr_t.trim(); + TCHECK(s_tr_t == "hello"); + + // --- trim() both --- + lString8 s_tr_b {" hello "}; + s_tr_b.trim(); + TCHECK(s_tr_b == "hello"); + + // --- trim() no spaces --- + lString8 s_tr_n {"hello"}; + s_tr_n.trim(); + TCHECK(s_tr_n == "hello"); + + // --- trim() all spaces --- + lString8 s_tr_as {" "}; + s_tr_as.trim(); + TCHECK(s_tr_as.empty()); + + // --- trim() empty --- + lString8 s_tr_e; + s_tr_e.trim(); + TCHECK(s_tr_e.empty()); + + // --- trim() tabs --- + lString8 s_tr_tab {"\thello\t"}; + s_tr_tab.trim(); + TCHECK(s_tr_tab == "hello"); + + // --- trim() mixed tabs/spaces --- + lString8 s_tr_mix {" \t hello \t "}; + s_tr_mix.trim(); + TCHECK(s_tr_mix == "hello"); + + // --- trim() single char --- + lString8 s_tr_sc {" x "}; + s_tr_sc.trim(); + TCHECK(s_tr_sc == "x"); + + // --- trim() on shared string (COW) --- + lString8 s_tr_shared {" shared copy "}; + lString8 s_tr_shared_copy = s_tr_shared; + s_tr_shared_copy.trim(); + TCHECK(s_tr_shared_copy == "shared copy"); + TCHECK(s_tr_shared == " shared copy "); + + printf("exiting of lString8 test\n"); +} + +void test_writable_refs_lString8() { + // --- writableRef() basic --- + lString8 s1 {"Original string"}; + lString8 s1_copy = s1; + TCHECK(s1 == s1_copy); + auto& s1_wr = s1.writableRef(100); + TCHECK(s1_wr.length() == s1_copy.length()); + + // --- data() const --- + lString8 s_data {"test"}; + auto& s_data_wr = s_data.writableRef(); + TCHECK(s_data_wr.data() != nullptr); + TCHECK(strcmp(s_data_wr.data(), "test") == 0); + + // --- data() non-const --- + lString8 s_data_nc {"mutable"}; + auto& s_data_nc_wr = s_data_nc.writableRef(); + char * p_data = s_data_nc_wr.data(); + TCHECK(p_data != nullptr); + p_data[0] = 'M'; + TCHECK(s_data_nc == "Mutable"); + + // --- clear() --- + lString8 s_clear {"clear me"}; + auto& s_clear_wr = s_clear.writableRef(); + s_clear_wr.clear(); + TCHECK(s_clear.empty()); + TCHECK(s_clear.length() == 0); + TCHECK(s_clear.capacity() == 0); + + // --- reset() --- + lString8 s_reset {"reset me"}; + auto& s_reset_wr = s_reset.writableRef(); + s_reset_wr.reset(50); + TCHECK(s_reset.empty()); + TCHECK(s_reset.length() == 0); + TCHECK(s_reset.capacity() >= 50); + + // --- reserve() --- + lString8 s_reserve {"reserve"}; + auto& s_reserve_wr = s_reserve.writableRef(); + s_reserve_wr.reserve(200); + TCHECK(s_reserve == "reserve"); + TCHECK(s_reserve.capacity() >= 200); + + // --- reserve() on empty --- + lString8 s_reserve_empty; + auto& s_reserve_empty_wr = s_reserve_empty.writableRef(); + s_reserve_empty_wr.reserve(100); + TCHECK(s_reserve_empty.empty()); + TCHECK(s_reserve_empty.capacity() >= 100); + + // --- pack() --- + lString8 s_pack {"pack"}; + auto& s_pack_wr = s_pack.writableRef(); + s_pack_wr.reserve(500); + TCHECK(s_pack.capacity() >= 500); + auto& s_pack_str = s_pack_wr.pack(); + TCHECK(s_pack_str == "pack"); + TCHECK(s_pack_str.capacity() < 500); + + // --- assign(ro_string_type&&) --- + lString8 s_assign_move {"move me"}; + lString8 s_assign_move_src {"source"}; + auto& s_assign_move_wr = s_assign_move.writableRef(); + s_assign_move_wr.assign(std::move(s_assign_move_src)); + TCHECK(s_assign_move == "source"); + TCHECK(s_assign_move_src.empty()); + + // --- assign(string_wr&&) --- + lString8 s_assign_wr_move {"target"}; + auto& s_assign_wr_move_wr = s_assign_wr_move.writableRef(); + lString8 s_src {"source2"}; + auto& s_src_wr = s_src.writableRef(); + s_assign_wr_move_wr.assign(std::move(s_src_wr)); + TCHECK(s_assign_wr_move == "source2"); + TCHECK(s_src.empty()); + + // --- assign(ro_string_type&) --- + lString8 s_assign_copy {"target2"}; + lString8 s_assign_copy_src {"copy source"}; + auto& s_assign_copy_wr = s_assign_copy.writableRef(); + s_assign_copy_wr.assign(s_assign_copy_src); + TCHECK(s_assign_copy == "copy source"); + TCHECK(s_assign_copy_src == "copy source"); + + // --- assign(ro_string_type&, offset, count) --- + lString8 s_assign_frag {"target3"}; + lString8 s_assign_frag_src {"abcdef"}; + auto& s_assign_frag_wr = s_assign_frag.writableRef(); + s_assign_frag_wr.assign(s_assign_frag_src, 1, 3); + TCHECK(s_assign_frag == "bcd"); + + // --- assign(char_type*) --- + lString8 s_assign_cstr {"old"}; + auto& s_assign_cstr_wr = s_assign_cstr.writableRef(); + s_assign_cstr_wr.assign("new string"); + TCHECK(s_assign_cstr == "new string"); + + // --- assign(char_type*, count) --- + lString8 s_assign_cstr_cnt {"old2"}; + auto& s_assign_cstr_cnt_wr = s_assign_cstr_cnt.writableRef(); + s_assign_cstr_cnt_wr.assign("partial", 3); + TCHECK(s_assign_cstr_cnt == "par"); + + // --- operator=(char_type*) --- + lString8 s_op_eq {"old3"}; + auto& s_op_eq_wr = s_op_eq.writableRef(); + s_op_eq_wr = "assigned"; + TCHECK(s_op_eq == "assigned"); + + // --- uppercase() --- + lString8 s_upper {"hello"}; + auto& s_upper_wr = s_upper.writableRef(); + s_upper_wr.uppercase(); + TCHECK(s_upper == "HELLO"); + + // --- lowercase() --- + lString8 s_lower {"HELLO"}; + auto& s_lower_wr = s_lower.writableRef(); + s_lower_wr.lowercase(); + TCHECK(s_lower == "hello"); + + // --- appendDecimal() --- + lString8 s_app_dec {"num: "}; + auto& s_app_dec_wr = s_app_dec.writableRef(); + s_app_dec_wr.appendDecimal(42); + TCHECK(s_app_dec == "num: 42"); + + // --- appendHex() --- + lString8 s_app_hex {"0x"}; + auto& s_app_hex_wr = s_app_hex.writableRef(); + s_app_hex_wr.appendHex(0xFF); + TCHECK(s_app_hex == "0xff"); + + // --- append(char_type*, count) --- + lString8 s_app_cnt {"hello"}; + auto& s_app_cnt_wr = s_app_cnt.writableRef(); + s_app_cnt_wr.append(" world", 6); + TCHECK(s_app_cnt == "hello world"); + + // --- append(char_type*) --- + lString8 s_app_cstr {"hello"}; + auto& s_app_cstr_wr = s_app_cstr.writableRef(); + s_app_cstr_wr.append(" world"); + TCHECK(s_app_cstr == "hello world"); + + // --- append(string_wr&) --- + lString8 s_app_wr1 {"hello"}; + lString8 s_app_wr2 {" world"}; + auto& s_app_wr1_wr = s_app_wr1.writableRef(); + auto& s_app_wr2_wr = s_app_wr2.writableRef(); + s_app_wr1_wr.append(s_app_wr2_wr); + TCHECK(s_app_wr1 == "hello world"); + + // --- append(string_wr&, offset, count) --- + lString8 s_app_wr_off {"start"}; + lString8 s_app_wr_off_src {"abcdef"}; + auto& s_app_wr_off_wr = s_app_wr_off.writableRef(); + auto& s_app_wr_off_src_wr = s_app_wr_off_src.writableRef(); + s_app_wr_off_wr.append(s_app_wr_off_src_wr, 1, 3); + TCHECK(s_app_wr_off == "startbcd"); + + // --- append(count, char) --- + lString8 s_app_n {"fill:"}; + auto& s_app_n_wr = s_app_n.writableRef(); + s_app_n_wr.append(3, 'X'); + TCHECK(s_app_n == "fill:XXX"); + + // --- operator << (char) --- + lString8 s_sl_ch {"a"}; + auto& s_sl_ch_wr = s_sl_ch.writableRef(); + s_sl_ch_wr << 'b'; + TCHECK(s_sl_ch == "ab"); + + // --- operator << (c-str) --- + lString8 s_sl_cstr {"hello"}; + auto& s_sl_cstr_wr = s_sl_cstr.writableRef(); + s_sl_cstr_wr << " world"; + TCHECK(s_sl_cstr == "hello world"); + + // --- operator << (string_wr) --- + lString8 s_sl_wr1 {"hello"}; + lString8 s_sl_wr2 {" world"}; + auto& s_sl_wr1_wr = s_sl_wr1.writableRef(); + auto& s_sl_wr2_wr = s_sl_wr2.writableRef(); + s_sl_wr1_wr << s_sl_wr2_wr; + TCHECK(s_sl_wr1 == "hello world"); + + // --- operator << (fmt::decimal) --- + lString8 s_sl_dec {"val:"}; + auto& s_sl_dec_wr = s_sl_dec.writableRef(); + s_sl_dec_wr << fmt::decimal(123); + TCHECK(s_sl_dec == "val:123"); + + // --- operator << (fmt::hex) --- + lString8 s_sl_hex {"0x"}; + auto& s_sl_hex_wr = s_sl_hex.writableRef(); + s_sl_hex_wr << fmt::hex(255); + TCHECK(s_sl_hex == "0xff"); + + // --- operator += (char) --- + lString8 s_plus_ch {"a"}; + auto& s_plus_ch_wr = s_plus_ch.writableRef(); + s_plus_ch_wr += 'b'; + TCHECK(s_plus_ch == "ab"); + + // --- operator += (c-str) --- + lString8 s_plus_cstr {"hello"}; + auto& s_plus_cstr_wr = s_plus_cstr.writableRef(); + s_plus_cstr_wr += " world"; + TCHECK(s_plus_cstr == "hello world"); + + // --- operator += (string_wr) --- + lString8 s_plus_wr1 {"hello"}; + lString8 s_plus_wr2 {" world"}; + auto& s_plus_wr1_wr = s_plus_wr1.writableRef(); + auto& s_plus_wr2_wr = s_plus_wr2.writableRef(); + s_plus_wr1_wr += s_plus_wr2_wr; + TCHECK(s_plus_wr1 == "hello world"); + + // --- operator += (fmt::decimal) --- + lString8 s_plus_dec {"val:"}; + auto& s_plus_dec_wr = s_plus_dec.writableRef(); + s_plus_dec_wr += fmt::decimal(456); + TCHECK(s_plus_dec == "val:456"); + + // --- operator += (fmt::hex) --- + lString8 s_plus_hex {"0x"}; + auto& s_plus_hex_wr = s_plus_hex.writableRef(); + s_plus_hex_wr += fmt::hex(0xAB); + TCHECK(s_plus_hex == "0xab"); + + // --- insert(char_type*, count) --- + lString8 s_ins_cnt {"helloworld"}; + auto& s_ins_cnt_wr = s_ins_cnt.writableRef(); + s_ins_cnt_wr.insert(5, " ", 1); + TCHECK(s_ins_cnt == "hello world"); + + // --- insert(char_type*) --- + lString8 s_ins_cstr {"helloworld"}; + auto& s_ins_cstr_wr = s_ins_cstr.writableRef(); + s_ins_cstr_wr.insert(5, " "); + TCHECK(s_ins_cstr == "hello world"); + + // --- insert(ro_string_type&) --- + lString8 s_ins_ro {"helloworld"}; + lString8 s_ins_ro_src {" "}; + auto& s_ins_ro_wr = s_ins_ro.writableRef(); + s_ins_ro_wr.insert(5, s_ins_ro_src); + TCHECK(s_ins_ro == "hello world"); + + // --- insert(count, char) --- + lString8 s_ins_n {"helloworld"}; + auto& s_ins_n_wr = s_ins_n.writableRef(); + s_ins_n_wr.insert(5, 3, '-'); + TCHECK(s_ins_n == "hello---world"); + + // --- replace(char_type*, count) --- + lString8 s_rep_cnt {"hello world"}; + auto& s_rep_cnt_wr = s_rep_cnt.writableRef(); + s_rep_cnt_wr.replace(6, 5, "earth", 5); + TCHECK(s_rep_cnt == "hello earth"); + + // --- replace(count, char) --- + lString8 s_rep_n {"hello world"}; + auto& s_rep_n_wr = s_rep_n.writableRef(); + s_rep_n_wr.replace(6, 5, 5, 'X'); + TCHECK(s_rep_n == "hello XXXXX"); + + // --- replace(char_type*) --- + lString8 s_rep_cstr {"hello world"}; + auto& s_rep_cstr_wr = s_rep_cstr.writableRef(); + s_rep_cstr_wr.replace(6, 5, "earth"); + TCHECK(s_rep_cstr == "hello earth"); + + // --- replace(ro_string_type&) --- + lString8 s_rep_ro {"hello world"}; + lString8 s_rep_ro_src {"earth"}; + auto& s_rep_ro_wr = s_rep_ro.writableRef(); + s_rep_ro_wr.replace(6, 5, s_rep_ro_src); + TCHECK(s_rep_ro == "hello earth"); + + // --- replace(ro_string_type&, offset, count) --- + lString8 s_rep_ro_off {"hello world"}; + lString8 s_rep_ro_off_src {"abcdefgh"}; + auto& s_rep_ro_off_wr = s_rep_ro_off.writableRef(); + s_rep_ro_off_wr.replace(6, 5, s_rep_ro_off_src, 2, 3); + TCHECK(s_rep_ro_off == "hello cde"); + + // --- replace(char, char) --- + lString8 s_rep_cc {"hello world"}; + auto& s_rep_cc_wr = s_rep_cc.writableRef(); + s_rep_cc_wr.replace('l', 'L'); + TCHECK(s_rep_cc == "heLLo worLd"); + + // --- erase() --- + lString8 s_erase {"hello world"}; + auto& s_erase_wr = s_erase.writableRef(); + s_erase_wr.erase(5, 6); + TCHECK(s_erase == "hello"); + + // --- trim() via writableRef --- + lString8 s_wr_tr {" hello "}; + auto& s_wr_tr_ref = s_wr_tr.writableRef(); + s_wr_tr_ref.trim(); + TCHECK(s_wr_tr == "hello"); + + // --- trim() via writableRef all spaces --- + lString8 s_wr_tr_as {" "}; + auto& s_wr_tr_as_ref = s_wr_tr_as.writableRef(); + s_wr_tr_as_ref.trim(); + TCHECK(s_wr_tr_as.empty()); + + // --- trim() via writableRef empty --- + lString8 s_wr_tr_e; + auto& s_wr_tr_e_ref = s_wr_tr_e.writableRef(); + s_wr_tr_e_ref.trim(); + TCHECK(s_wr_tr_e.empty()); + + // --- trim() via writableRef no-op --- + lString8 s_wr_tr_no {"hello"}; + auto& s_wr_tr_no_ref = s_wr_tr_no.writableRef(); + s_wr_tr_no_ref.trim(); + TCHECK(s_wr_tr_no == "hello"); +} + +void test_lstring2_unicode() { + printf("running test_lstring2_unicode()\n"); + + // --- begin()/end() basic --- + { + lString8 s_begin{"hello"}; + int count = 0; + for (auto c : s_begin) + count++; + TCHECK(count == 5); + } + + // --- begin()/end() empty --- + { + lString8 s_begin_empty; + int count = 0; + for (auto c : s_begin_empty) + count++; + TCHECK(count == 0); + } + + // --- begin()/end() with embedded null --- + { + lString8 s_embed{"ab\0cd", 5, 5}; + int count = 0; + for (auto c : s_embed) + count++; + TCHECK(count == 5); + } + + // --- unicodeRange() ASCII --- + { + lString8 s_ascii{"hello"}; + int count = 0; + for (auto cp : s_ascii.unicodeRange()) { + TCHECK(cp < 0x80); + count++; + } + TCHECK(count == 5); + } + + // --- unicodeRange() utf8 2-byte --- + { + // U+00E9 "é" = 0xC3 0xA9 in UTF-8 + lString8 s_2byte{"\xC3\xA9"}; + int count = 0; + for (auto cp : s_2byte.unicodeRange()) { + TCHECK(cp == 0xE9); + count++; + } + TCHECK(count == 1); + TCHECK(s_2byte.length() == 2); // 2 utf8 bytes + } + + // --- unicodeRange() utf8 3-byte --- + { + // U+20AC "€" = 0xE2 0x82 0xAC in UTF-8 + lString8 s_3byte{"\xE2\x82\xAC"}; + int count = 0; + for (auto cp : s_3byte.unicodeRange()) { + TCHECK(cp == 0x20AC); + count++; + } + TCHECK(count == 1); + TCHECK(s_3byte.length() == 3); + } + + // --- unicodeRange() utf8 4-byte --- + { + // U+1D11E "𝄞" = 0xF0 0x9D 0x84 0x9E in UTF-8 + lString8 s_4byte{"\xF0\x9D\x84\x9E"}; + int count = 0; + for (auto cp : s_4byte.unicodeRange()) { + TCHECK(cp == 0x1D11E); + count++; + } + TCHECK(count == 1); + TCHECK(s_4byte.length() == 4); + } + + // --- unicodeRange() mixed utf8 --- + { + // "héllo" — h(1) é(2) l(1) l(1) o(1) = 6 bytes, 5 codepoints + lString8 s_mixed{"h\xC3\xA9llo"}; + int count = 0; + lChar32 expected[] = {'h', 0xE9, 'l', 'l', 'o'}; + int i = 0; + for (auto cp : s_mixed.unicodeRange()) { + TCHECK(cp == expected[i]); + i++; + count++; + } + TCHECK(count == 5); + TCHECK(s_mixed.length() == 6); + } + + // --- unicodeRange() invalid utf8 (continuation byte without leader) --- + { + lString8 s_invalid{"\x80"}; + for (auto cp : s_invalid.unicodeRange()) + TCHECK(cp == lString8::invalid_unicode); + } + + // --- unicodeRange() invalid utf8 (unexpected non-continuation) --- + { + // 0xC3 expects continuation, but next is 'A' (0x41 has no 0x80 bit set) + lString8 s_badseq{"\xC3" "A"}; + for (auto cp : s_badseq.unicodeRange()) + TCHECK(cp == lString8::invalid_unicode); + } + + // --- unicodeRange() overlong sequence (3-byte for 2-byte char) --- + { + // overlong encoding of '/' (U+002F) as 0xE0 0x80 0xAF + // overlong encoding considered as valid + lString8 s_overlong{"\xE0\x80\xAF"}; + for (auto cp : s_overlong.unicodeRange()) + TCHECK(cp == '/'); //lString8::invalid_unicode); + } + + // --- unicodeRange() empty string --- + { + lString8 s_empty; + int count = 0; + for (auto cp : s_empty.unicodeRange()) + count++; + TCHECK(count == 0); + } + + // --- unicodeRange() subrange --- + { + lString8 s_sub{"hello world"}; + int count = 0; + for (auto cp : s_sub.unicodeRange(6, 5)) { + count++; + } + TCHECK(count == 5); + } + + // --- unicodeRange() subrange clamped --- + { + lString8 s_sub2{"abcdef"}; + int count = 0; + for (auto cp : s_sub2.unicodeRange(4, 10)) { + count++; + } + TCHECK(count == 2); // only "ef" remains + } + + // --- unicodeRange() subrange empty (start out of bounds) --- + { + lString8 s_sub3{"abc"}; + int count = 0; + for (auto cp : s_sub3.unicodeRange(5, 3)) + count++; + TCHECK(count == 0); + } + + // --- utfCodePointSize for lChar8 --- + TCHECK((utfCodePointSize(0x0061)) == 1); + TCHECK((utfCodePointSize(0x00E9)) == 2); + TCHECK((utfCodePointSize(0x20AC)) == 3); + TCHECK((utfCodePointSize(0x1D11E)) == 4); + + // --- utfCodePointSize for lChar16 --- + TCHECK((utfCodePointSize(0x0061)) == 1); +// TCHECK((utfCodePointSize(0xD800)) == 2); // lone surrogate → needs pair + TCHECK((utfCodePointSize(0x1D11E)) == 2); + + // --- utfCodePointSize for lChar32 --- + TCHECK((utfCodePointSize(0x0061)) == 1); + TCHECK((utfCodePointSize(0x10FFFF)) == 1); + + // --- utfReadCodePoint end_of_stream --- + { + const lChar8 * s = nullptr; + const lChar8 * end = nullptr; + TCHECK(utfReadCodePoint(s, end) == 0xFFFFFFFFu); + } + + // --- utfReadCodePoint ASCII through lString16 --- + { + // test the template with lChar16 type explicitly + lChar16 buf[] = { 'h', 'e', 'l', 'l', 'o' }; + const lChar16 * s = buf; + const lChar16 * end = buf + 5; + TCHECK(utfReadCodePoint(s, end) == 'h'); + TCHECK(s == buf + 1); + } + + // --- utfReadCodePoint lChar32 --- + { + lChar32 buf[] = { 0x20AC, 0x1D11E }; + const lChar32 * s = buf; + const lChar32 * end = buf + 2; + TCHECK(utfReadCodePoint(s, end) == 0x20AC); + TCHECK(s == buf + 1); + TCHECK(utfReadCodePoint(s, end) == 0x1D11E); + TCHECK(s == buf + 2); + TCHECK(utfReadCodePoint(s, end) == 0xFFFFFFFFu); + } + + // --- CodepointReadIterator basic --- + { + lString8 s_iter{"hi"}; + int count = 0; + for (auto cp : s_iter.unicodeRange()) { + (void)cp; + count++; + } + TCHECK(count == 2); + } + + // --- CodepointReadIterator with utf8 --- + { + lString8 s_utf{"\xC3\xA9\xC3\xA0"}; // éà + int count = 0; + for (auto cp : s_utf.unicodeRange()) { + (void)cp; + count++; + } + TCHECK(count == 2); + } + + // --- unicodeRange() subrange --- + { + lString8 s_sub{"hello world"}; + int count = 0; + for (auto cp : s_sub.unicodeRange(6, 5)) { + (void)cp; + count++; + } + TCHECK(count == 5); + } + + // --- unicodeRange() subrange clamped --- + { + lString8 s_sub2{"abcdef"}; + int count = 0; + for (auto cp : s_sub2.unicodeRange(4, 10)) { + (void)cp; + count++; + } + TCHECK(count == 2); // only "ef" remains + } + + // --- unicodeRange() subrange empty --- + { + lString8 s_sub3{"abc"}; + int count = 0; + for (auto cp : s_sub3.unicodeRange(5, 3)) + count++; + TCHECK(count == 0); + } + + // --- codePointCount() empty --- + { + lString8 s; + TCHECK(s.codePointCount() == 0); + } + + // --- codePointCount() ASCII --- + { + lString8 s{"hello"}; + TCHECK(s.codePointCount() == 5); + } + + // --- codePointCount() utf8 2-byte --- + { + lString8 s{"\xC3\xA9"}; // U+00E9 + TCHECK(s.codePointCount() == 1); + TCHECK(s.length() == 2); // 2 utf8 bytes + } + + // --- codePointCount() utf8 3-byte --- + { + lString8 s{"\xE2\x82\xAC"}; // U+20AC + TCHECK(s.codePointCount() == 1); + TCHECK(s.length() == 3); + } + + // --- codePointCount() utf8 4-byte --- + { + lString8 s{"\xF0\x9D\x84\x9E"}; // U+1D11E + TCHECK(s.codePointCount() == 1); + TCHECK(s.length() == 4); + } + + // --- codePointCount() mixed utf8 --- + { + // "héllo" — h(1) é(2) l(1) l(1) o(1) = 6 bytes, 5 codepoints + lString8 s{"h\xC3\xA9llo"}; + TCHECK(s.codePointCount() == 5); + TCHECK(s.length() == 6); + } + + // --- codePointCount() invalid utf8 --- + { + // invalid continuation byte — still counted as 1 codepoint (invalid) + lString8 s{"\x80"}; + TCHECK(s.codePointCount() == 1); + } + + // --- codePointCount() all whitespace --- + { + lString8 s{" \t "}; + TCHECK(s.codePointCount() == 6); + } + + // --- codePointCount() subrange --- + { + lString8 s{"hello world"}; + TCHECK(s.codePointCount(6, 5) == 5); // "world" + } + + // --- codePointCount() subrange clamped --- + { + lString8 s{"abcdef"}; + TCHECK(s.codePointCount(4, 10) == 2); // "ef" + } + + // --- codePointCount() subrange empty --- + { + lString8 s{"abc"}; + TCHECK(s.codePointCount(5, 3) == 0); // start OOB + } + + // --- utfCodePointCount free function, lChar8 --- + { + const lChar8 buf[] = "hello"; + TCHECK(utfCodePointCount(buf, buf + 5) == 5); + } + + // --- utfCodePointCount free function, lChar16 --- + { + lChar16 buf[] = { 'h', 'e', 'l', 'l', 'o' }; + TCHECK(utfCodePointCount(buf, buf + 5) == 5); + } + + // --- utfCodePointCount free function, lChar32 --- + { + lChar32 buf[] = { 0x20AC, 0x1D11E, 'A' }; + TCHECK(utfCodePointCount(buf, buf + 3) == 3); + } + + // --- utfCodePointCount empty --- + { + const lChar8 * p = nullptr; + TCHECK(utfCodePointCount(p, p) == 0); + } + + // --- utfWriteCodePoint roundtrip lChar8 utf8 1-byte --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0x41, s, end); + TCHECK(result == 0x41); + TCHECK(s == buf + 1); + const lChar8 * r = buf; + lChar32 cp = utfReadCodePoint(r, s); + TCHECK(cp == 0x41); + } + + // --- utfWriteCodePoint roundtrip lChar8 utf8 2-byte --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0xE9, s, end); + TCHECK(result == 0xE9); + TCHECK(s == buf + 2); + const lChar8 * r = buf; + lChar32 cp = utfReadCodePoint(r, s); + TCHECK(cp == 0xE9); + } + + // --- utfWriteCodePoint roundtrip lChar8 utf8 3-byte --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0x20AC, s, end); + TCHECK(result == 0x20AC); + TCHECK(s == buf + 3); + const lChar8 * r = buf; + lChar32 cp = utfReadCodePoint(r, s); + TCHECK(cp == 0x20AC); + } + + // --- utfWriteCodePoint roundtrip lChar8 utf8 4-byte --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0x1D11E, s, end); + TCHECK(result == 0x1D11E); + TCHECK(s == buf + 4); + const lChar8 * r = buf; + lChar32 cp = utfReadCodePoint(r, s); + TCHECK(cp == 0x1D11E); + } + + // --- utfWriteCodePoint lChar8 boundary values --- + { + lChar8 buf[8]; + lChar8 * s, * end = buf + 8; + + s = buf; utfWriteCodePoint(0x7F, s, end); TCHECK(s == buf + 1); + s = buf; utfWriteCodePoint(0x80, s, end); TCHECK(s == buf + 2); + s = buf; utfWriteCodePoint(0x7FF, s, end); TCHECK(s == buf + 2); + s = buf; utfWriteCodePoint(0x800, s, end); TCHECK(s == buf + 3); + s = buf; utfWriteCodePoint(0xFFFF, s, end); TCHECK(s == buf + 3); + s = buf; utfWriteCodePoint(0x10000, s, end); TCHECK(s == buf + 4); + s = buf; utfWriteCodePoint(0x10FFFF, s, end); TCHECK(s == buf + 4); + } + + // --- utfWriteCodePoint lChar8 invalid: surrogate --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0xD800, s, end); + TCHECK(result == 0xFFFFFFFEu); // invalid_char + TCHECK(s == buf); // not advanced + } + + // --- utfWriteCodePoint lChar8 invalid: > 0x10FFFF --- + { + lChar8 buf[8]; + lChar8 * s = buf; + const lChar8 * end = buf + 8; + lChar32 result = utfWriteCodePoint(0x110000, s, end); + TCHECK(result == 0xFFFFFFFEu); // invalid_char + TCHECK(s == buf); + } + + // --- utfWriteCodePoint lChar8 buffer full --- + { + lChar8 buf[4]; + lChar8 * s, * end = buf + 4; + + s = buf; TCHECK(utfWriteCodePoint(0x41, s, end) == 0x41); // 1 byte, fits + s = buf; TCHECK(utfWriteCodePoint(0xE9, s, end) == 0xE9); // 2 bytes, fits + s = buf; TCHECK(utfWriteCodePoint(0x20AC, s, end) == 0x20AC); // 3 bytes, fits + s = buf; TCHECK(utfWriteCodePoint(0x1D11E, s, end) == 0x1D11E); // 4 bytes, fits + + s = buf + 3; TCHECK(utfWriteCodePoint(0x20AC, s, end) == 0xFFFFFFFFu); // needs 3, only 1 left + s = buf + 1; TCHECK(utfWriteCodePoint(0x1D11E, s, end) == 0xFFFFFFFFu); // needs 4, only 3 left + } + + // --- utfWriteCodePoint roundtrip lChar16 utf16 BMP --- + { + lChar16 buf[4]; + lChar16 * s = buf; + const lChar16 * end = buf + 4; + lChar32 result = utfWriteCodePoint(0x41, s, end); + TCHECK(result == 0x41); + TCHECK(s == buf + 1); + const lChar16 * r = buf; + TCHECK(utfReadCodePoint(r, s) == 0x41); + } + + // --- utfWriteCodePoint roundtrip lChar16 utf16 supplementary --- + { + lChar16 buf[4]; + lChar16 * s = buf; + const lChar16 * end = buf + 4; + lChar32 result = utfWriteCodePoint(0x1D11E, s, end); + TCHECK(result == 0x1D11E); + TCHECK(s == buf + 2); + const lChar16 * r = buf; + TCHECK(utfReadCodePoint(r, s) == 0x1D11E); + } + + // --- utfWriteCodePoint lChar16 max valid U+10FFFF --- + { + lChar16 buf[4]; + lChar16 * s = buf; + const lChar16 * end = buf + 4; + lChar32 result = utfWriteCodePoint(0x10FFFF, s, end); + TCHECK(result == 0x10FFFF); + TCHECK(s == buf + 2); // surrogate pair + const lChar16 * r = buf; + TCHECK(utfReadCodePoint(r, s) == 0x10FFFF); + } + + // --- utfWriteCodePoint lChar16 invalid: surrogate --- + { + lChar16 buf[4]; + lChar16 * s = buf; + const lChar16 * end = buf + 4; + TCHECK(utfWriteCodePoint(0xD800, s, end) == 0xFFFFFFFEu); + TCHECK(s == buf); + } + + // --- utfWriteCodePoint lChar16 invalid: > 0x10FFFF --- + { + lChar16 buf[4]; + lChar16 * s = buf; + const lChar16 * end = buf + 4; + TCHECK(utfWriteCodePoint(0x110000, s, end) == 0xFFFFFFFEu); + TCHECK(s == buf); + } + + // --- utfWriteCodePoint lChar16 buffer full --- + { + lChar16 buf[2]; + lChar16 * s, * end = buf + 2; + + s = buf; TCHECK(utfWriteCodePoint(0x41, s, end) == 0x41); // 1 word, fits + s = buf; TCHECK(utfWriteCodePoint(0x1D11E, s, end) == 0x1D11E); // 2 words, fits + + s = buf + 1; TCHECK(utfWriteCodePoint(0x1D11E, s, end) == 0xFFFFFFFFu); // needs 2, only 1 left + s = buf; TCHECK(utfWriteCodePoint(0x41, s, end) == 0x41); // 1 word still fits + } + + // --- utfWriteCodePoint roundtrip lChar32 --- + { + lChar32 buf[4]; + lChar32 * s = buf; + const lChar32 * end = buf + 4; + lChar32 result = utfWriteCodePoint(0x1D11E, s, end); + TCHECK(result == 0x1D11E); + TCHECK(s == buf + 1); + const lChar32 * r = buf; + TCHECK(utfReadCodePoint(r, s) == 0x1D11E); + } + + // --- utfWriteCodePoint lChar32 invalid --- + { + lChar32 buf[4]; + lChar32 * s = buf; + const lChar32 * end = buf + 4; + TCHECK(utfWriteCodePoint(0xD800, s, end) == 0xFFFFFFFEu); + TCHECK(s == buf); + TCHECK(utfWriteCodePoint(0x110000, s, end) == 0xFFFFFFFEu); + TCHECK(s == buf); + } + + // --- utfWriteCodePoint lChar32 buffer full --- + { + lChar32 buf[1]; + lChar32 * s = buf; + const lChar32 * end = buf + 1; + TCHECK(utfWriteCodePoint(0x41, s, end) == 0x41); + // s is now at end (buf + 1), no space left + TCHECK(utfWriteCodePoint(0x1D11E, s, end) == 0xFFFFFFFFu); + } + + // --- utfConvDestBufferSize lChar8→lChar8 identity --- + { + const lChar8 src[] = "hello"; + TCHECK((utfConvDestBufferSize(src, src + 5) == 5)); + } + + // --- utfConvDestBufferSize lChar8→lChar8 with utf8 --- + { + const lChar8 src[] = "h\xC3\xA9llo"; + TCHECK((utfConvDestBufferSize(src, src + 6) == 6)); + } + + // --- utfConvDestBufferSize lChar8→lChar16 --- + { + const lChar8 src[] = "h\xC3\xA9llo"; + TCHECK((utfConvDestBufferSize(src, src + 6) == 5)); + } + + // --- utfConvDestBufferSize lChar8→lChar32 --- + { + const lChar8 src[] = "h\xC3\xA9llo"; + TCHECK((utfConvDestBufferSize(src, src + 6) == 5)); + } + + // --- utfConvDestBufferSize lChar16→lChar8 --- + { + const lChar16 src[] = { 'h', 0xE9, 'l', 'l', 'o' }; + TCHECK((utfConvDestBufferSize(src, src + 5) == 6)); + } + + // --- utfConvDestBufferSize lChar16→lChar16 identity --- + { + const lChar16 src[] = { 'h', 0xE9, 'l', 'l', 'o' }; + TCHECK((utfConvDestBufferSize(src, src + 5) == 5)); + } + + // --- utfConvDestBufferSize lChar32→lChar8 supplementary --- + { + const lChar32 src[] = { 0x1D11E, 'A' }; + TCHECK((utfConvDestBufferSize(src, src + 2) == 5)); + } + + // --- utfConvDestBufferSize with invalid codepoints filtered out --- + { + const lChar32 src[] = { 0xD800, 'A', 0x110000 }; + TCHECK((utfConvDestBufferSize(src, src + 3) == 1)); + } + + // --- utfConvDestBufferSize empty --- + { + const lChar8 * p = nullptr; + TCHECK((utfConvDestBufferSize(p, p) == 0)); + } + + // --- utfConvert lChar8→lChar8 identity --- + { + const lChar8 src[] = "hello"; + lChar8 dst[8] = {}; + const lChar8 * s = src; + lChar8 * d = dst; + const lChar8 * se = src + 5; + const lChar8 * de = dst + 8; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 5); + TCHECK(err == 0); + TCHECK(dst[0] == 'h' && dst[4] == 'o'); + TCHECK(s == src + 5); + TCHECK(d == dst + 5); + } + + // --- utfConvert lChar8→lChar16 --- + { + const lChar8 src[] = "h\xC3\xA9llo"; + lChar16 dst[8] = {}; + const lChar8 * s = src; + lChar16 * d = dst; + const lChar8 * se = src + 6; + const lChar16 * de = dst + 8; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 5); + TCHECK(err == 0); + TCHECK(dst[0] == 'h' && dst[1] == 0xE9 && dst[2] == 'l'); + TCHECK(s == src + 6); + } + + // --- utfConvert lChar16→lChar8 --- + { + const lChar16 src[] = { 0xD834, 0xDD1E, 'A' }; + lChar8 dst[8] = {}; + const lChar16 * s = src; + lChar8 * d = dst; + const lChar16 * se = src + 3; + const lChar8 * de = dst + 8; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 5); + TCHECK(err == 0); + TCHECK(dst[0] == (char)0xF0 && dst[1] == (char)0x9D && dst[2] == (char)0x84 && dst[3] == (char)0x9E); + TCHECK(dst[4] == 'A'); + } + + // --- utfConvert filters out invalid codepoints --- + { + const lChar32 src[] = { 0xD800, 'B', 0x110000, 'C' }; + lChar8 dst[8] = {}; + const lChar32 * s = src; + lChar8 * d = dst; + const lChar32 * se = src + 4; + const lChar8 * de = dst + 8; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 2); + TCHECK(err == 2); + TCHECK(dst[0] == 'B' && dst[1] == 'C'); + } + + // --- utfConvert buffer full mid-conversion --- + { + const lChar8 src[] = "hello"; + lChar8 dst[3] = {}; + const lChar8 * s = src; + lChar8 * d = dst; + const lChar8 * se = src + 5; + const lChar8 * de = dst + 3; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 3); + TCHECK(err == 0); + TCHECK(dst[0] == 'h' && dst[1] == 'e' && dst[2] == 'l'); + TCHECK(s > src); + } + + // --- utfConvert empty source --- + { + const lChar8 * src = nullptr; + lChar8 dst[8] = {}; + const lChar8 * s = src; + lChar8 * d = dst; + const lChar8 * se = src; + const lChar8 * de = dst + 8; + lUInt32 err = 999; + lUInt32 written = utfConvert(s, se, d, de, err); + TCHECK(written == 0); + TCHECK(err == 0); + } + + // --- assignUtf string lChar8→lString8 --- + { + lString8 s; + const lChar8 src[] = "hello"; + s.assignUtf(src, 5); + TCHECK(s == "hello"); + } + + // --- assignUtf string lChar8→lString8 with utf8 --- + { + lString8 s; + const lChar8 src[] = "h\xC3\xA9llo"; + s.assignUtf(src, 6); + TCHECK(s.length() == 6); + TCHECK(s[0] == 'h' && s[1] == (char)0xC3 && s[2] == (char)0xA9); + } + + // --- assignUtf string lChar16→lString8 --- + { + lString8 s; + const lChar16 src[] = { 'h', 0xE9, 'l', 'l', 'o' }; + s.assignUtf(src, 5); + TCHECK(s.length() == 6); + TCHECK(s[0] == 'h' && s[1] == (char)0xC3 && s[2] == (char)0xA9); + TCHECK(s[3] == 'l' && s[4] == 'l' && s[5] == 'o'); + } + + // --- assignUtf string lChar16 supplementary→lString8 --- + { + lString8 s; + const lChar16 src[] = { 0xD834, 0xDD1E, 'A' }; + s.assignUtf(src, 3); + TCHECK(s.length() == 5); + TCHECK(s[0] == (char)0xF0 && s[1] == (char)0x9D && s[2] == (char)0x84 && s[3] == (char)0x9E); + TCHECK(s[4] == 'A'); + } + + // --- assignUtf string lChar32→lString8 --- + { + lString8 s; + const lChar32 src[] = { 0x1D11E, 'B' }; + s.assignUtf(src, 2); + TCHECK(s.length() == 5); + TCHECK(s[0] == (char)0xF0 && s[1] == (char)0x9D); + } + + // --- assignUtf string with invalid codepoints filtered --- + { + lString8 s; + const lChar32 src[] = { 0xD800, 'X', 0x110000, 'Y' }; + s.assignUtf(src, 4); + TCHECK(s == "XY"); + TCHECK(s.length() == 2); + } + + // --- assignUtf string empty --- + { + lString8 s{"existing"}; + s.assignUtf(nullptr, 0); + TCHECK(s.empty()); + } + + // --- assignUtf string with npos (null-terminated) --- + { + lString8 s; + const lChar8 src[] = "hello"; + s.assignUtf(src); + TCHECK(s == "hello"); + } + + // --- assignUtf string_wr lChar8→lString8 --- + { + lString8 s; + auto& wr = s.writableRef(); + const lChar8 src[] = "world"; + wr.assignUtf(src, 5); + TCHECK(s == "world"); + } + + // --- assignUtf string_wr lChar8→lString8 with utf8 --- + { + lString8 s; + auto& wr = s.writableRef(); + const lChar8 src[] = "h\xC3\xA9llo"; + wr.assignUtf(src, 6); + TCHECK(s.length() == 6); + } + + // --- assignUtf string_wr empty (doesn't clear buffer) --- + { + lString8 s{"existing"}; + auto& wr = s.writableRef(); + wr.assignUtf(nullptr, 0); + TCHECK(s.empty()); + TCHECK(s.length() == 0); + } + + // --- assignUtf string COW: original unchanged --- + { + lString8 orig{"original"}; + lString8 copy = orig; + copy.assignUtf("new", 3); + TCHECK(copy == "new"); + TCHECK(orig == "original"); + } + + // --- 9-way assignUtf: lString8/16/32 ← utf8/16/32 --- + + // lString8 ← utf8 + { + lString8 s; + s.assignUtf("hello", 5); + TCHECK(s == "hello"); + } + // lString8 ← utf16 + { + lString8 s; + const lChar16 src[] = { 'h', 0xE9, 'l', 'l', 'o' }; + s.assignUtf(src, 5); + TCHECK(s.length() == 6); + TCHECK(s[0]=='h' && s[1]==(char)0xC3 && s[2]==(char)0xA9); + } + // lString8 ← utf32 + { + lString8 s; + const lChar32 src[] = { 'h', 0xE9, 0x1D11E }; + s.assignUtf(src, 3); + TCHECK(s.length() == 7); // 1 + 2 + 4 + TCHECK(s[0]=='h'); + } + + // lString16 ← utf8 + { + lString16 s; + s.assignUtf("hi", 2); + TCHECK(s[0]=='h' && s[1]=='i'); + } + // lString16 ← utf16 + { + lString16 s; + const lChar16 src[] = { 'x', 0xE9 }; + s.assignUtf(src, 2); + TCHECK(s[0]=='x' && s[1]==0xE9); + } + // lString16 ← utf32 (supplementary → surrogate pair) + { + lString16 s; + const lChar32 src[] = { 0x1D11E, 'Z' }; + s.assignUtf(src, 2); + TCHECK(s.length() == 3); // surrogate pair + 'Z' + TCHECK(s[0]==0xD834 && s[1]==0xDD1E && s[2]=='Z'); + } + + // lString32 ← utf8 + { + lString32 s; + s.assignUtf("hi", 2); + TCHECK(s[0]=='h' && s[1]=='i'); + } + // lString32 ← utf16 + { + lString32 s; + const lChar16 src[] = { 0xD834, 0xDD1E, 'Z' }; + s.assignUtf(src, 3); + TCHECK(s.length() == 2); // 2 codepoints + TCHECK(s[0]==0x1D11E && s[1]=='Z'); + } + // lString32 ← utf32 + { + lString32 s; + const lChar32 src[] = { 0x1D11E, 'Z' }; + s.assignUtf(src, 2); + TCHECK(s.length() == 2); + TCHECK(s[0]==0x1D11E && s[1]=='Z'); + } + + // --- appendUtf: empty string (null pchunk) --- + { + lString8 s; + s.appendUtf("hello", 5); + TCHECK(s == "hello"); + } + + // --- appendUtf: buffer fits (reuse) --- + { + lString8 s{"abc"}; + const lChar8 src[] = "xyz"; + s.appendUtf(src, 3); + TCHECK(s == "abcxyz"); + TCHECK(s.length() == 6); + } + + // --- appendUtf: buffer needs expansion --- + { + lString8 s{"abc"}; + s.reserve(4); // small capacity + s.appendUtf("xyz", 3); + TCHECK(s == "abcxyz"); + TCHECK(s.length() == 6); + } + + // --- appendUtf: COW isolation --- + { + lString8 orig{"original"}; + lString8 copy = orig; + copy.appendUtf("+more", 5); + TCHECK(copy == "original+more"); + TCHECK(orig == "original"); + } + + // --- appendUtf: sequential appends --- + { + lString8 s; + s.appendUtf("a", 1); + s.appendUtf("b", 1); + s.appendUtf("c", 1); + TCHECK(s == "abc"); + } + + // --- appendUtf: string_wr via writableRef --- + { + lString8 s{"base"}; + auto& wr = s.writableRef(); + wr.appendUtf("+ext", 4); + TCHECK(s == "base+ext"); + } + + // --- 9-way appendUtf: lString8/16/32 × utf8/16/32 --- + + // lString8 += utf8 + { + lString8 s{"AB"}; + s.appendUtf("\xC3\xA9", 2); + TCHECK(s.length() == 4); // A B é + TCHECK(s[0]=='A' && s[1]=='B' && s[2]==(char)0xC3 && s[3]==(char)0xA9); + } + // lString8 += utf16 + { + lString8 s{"AB"}; + const lChar16 src[] = { 0xE9, 'X' }; + s.appendUtf(src, 2); + TCHECK(s.length() == 5); // A B (2) + é(2) + X(1) + TCHECK(s[0]=='A' && s[1]=='B'); + } + // lString8 += utf32 + { + lString8 s{"AB"}; + const lChar32 src[] = { 0xE9 }; + s.appendUtf(src, 1); + TCHECK(s.length() == 4); // A B é + TCHECK(s[2]==(char)0xC3 && s[3]==(char)0xA9); + } + + // lString16 += utf8 + { + lString16 s; + s.assignUtf("AB", 2); + s.appendUtf("\xC3\xA9", 2); + TCHECK(s.length() == 3); + TCHECK(s[0]=='A' && s[1]=='B' && s[2]==0xE9); + } + // lString16 += utf16 + { + lString16 s; + const lChar16 ab[] = { 'A', 'B' }; + s.assignUtf(ab, 2); + const lChar16 xy[] = { 'X', 'Y' }; + s.appendUtf(xy, 2); + TCHECK(s.length() == 4); + TCHECK(s[0]=='A' && s[1]=='B' && s[2]=='X' && s[3]=='Y'); + } + // lString16 += utf32 (supplementary → surrogate pair) + { + lString16 s; + const lChar16 ab[] = { 'A', 'B' }; + s.assignUtf(ab, 2); + const lChar32 music[] = { 0x1D11E }; + s.appendUtf(music, 1); + TCHECK(s.length() == 4); + TCHECK(s[0]=='A' && s[1]=='B' && s[2]==0xD834 && s[3]==0xDD1E); + } + + // lString32 += utf8 + { + lString32 s; + s.assignUtf("AB", 2); + s.appendUtf("\xC3\xA9", 2); + TCHECK(s.length() == 3); + TCHECK(s[0]=='A' && s[1]=='B' && s[2]==0xE9); + } + // lString32 += utf16 + { + lString32 s; + s.assignUtf("AB", 2); + const lChar16 surrogate[] = { 0xD834, 0xDD1E }; + s.appendUtf(surrogate, 2); + TCHECK(s.length() == 3); + TCHECK(s[0]=='A' && s[1]=='B' && s[2]==0x1D11E); + } + // lString32 += utf32 + { + lString32 s; + const lChar32 ab[] = { 0x41, 0x1D11E }; + s.assignUtf(ab, 2); + const lChar32 last[] = { 0x10FFFF }; + s.appendUtf(last, 1); + TCHECK(s.length() == 3); + TCHECK(s[0]==0x41 && s[1]==0x1D11E && s[2]==0x10FFFF); + } + + // --- compareUtf same type (delegates to compare) --- + { + lString8 a{"abc"}, b{"abc"}, c{"abd"}; + TCHECK(a.compareUtf(b) == 0); + TCHECK(a.compareUtf(c) < 0); + TCHECK(c.compareUtf(a) > 0); + } + + // --- compareUtf lString8 vs lString16 --- + { + lString8 a{"abc"}; + lString16 b; + const lChar16 abc[] = { 'a', 'b', 'c' }; + b.assignUtf(abc, 3); + TCHECK(a.compareUtf(b) == 0); + TCHECK(b.compareUtf(a) == 0); + } + // --- compareUtf lString8 vs lString16 (less/greater) --- + { + lString8 a{"abc"}; + lString16 b; + const lChar16 abd[] = { 'a', 'b', 'd' }; + b.assignUtf(abd, 3); + TCHECK(a.compareUtf(b) < 0); + TCHECK(b.compareUtf(a) > 0); + } + + // --- compareUtf lString8 vs lString32 --- + { + lString8 a{"abc"}; + lString32 b; + const lChar32 abc[] = { 'a', 'b', 'c' }; + b.assignUtf(abc, 3); + TCHECK(a.compareUtf(b) == 0); + TCHECK(b.compareUtf(a) == 0); + } + // --- compareUtf lString8 vs lString32 (less/greater) --- + { + lString8 a{"abc"}; + lString32 b; + const lChar32 abd[] = { 'a', 'b', 'd' }; + b.assignUtf(abd, 3); + TCHECK(a.compareUtf(b) < 0); + TCHECK(b.compareUtf(a) > 0); + } + + // --- compareUtf lString16 vs lString32 --- + { + lString16 a; + const lChar16 abc16[] = { 'a', 'b', 'c' }; + a.assignUtf(abc16, 3); + lString32 b; + const lChar32 abc32[] = { 'a', 'b', 'c' }; + b.assignUtf(abc32, 3); + TCHECK(a.compareUtf(b) == 0); + TCHECK(b.compareUtf(a) == 0); + } + // --- compareUtf lString16 vs lString32 (less/greater) --- + { + lString16 a; + const lChar16 abc16[] = { 'a', 'b', 'c' }; + a.assignUtf(abc16, 3); + lString32 b; + const lChar32 abd32[] = { 'a', 'b', 'd' }; + b.assignUtf(abd32, 3); + TCHECK(a.compareUtf(b) < 0); + TCHECK(b.compareUtf(a) > 0); + } + + // --- compareUtf empty vs non-empty --- + { + lString8 empty; + lString16 nonempty; + const lChar16 x[] = { 'x' }; + nonempty.assignUtf(x, 1); + TCHECK(empty.compareUtf(nonempty) < 0); + TCHECK(nonempty.compareUtf(empty) > 0); + } + + // --- compareUtf both empty --- + { + lString8 a; + lString16 b; + TCHECK(a.compareUtf(b) == 0); + } + + // --- compareUtf different codepoints (utf8 vs utf32 with non-ASCII) --- + { + lString8 a; + a.assignUtf("\xC3\xA9", 2); // U+00E9 + lString32 b; + const lChar32 e32[] = { 0xE9 }; + b.assignUtf(e32, 1); + TCHECK(a.compareUtf(b) == 0); + TCHECK(b.compareUtf(a) == 0); + } + + // --- compareUtf supplementary characters --- + { + lString16 a; + const lChar16 music16[] = { 0xD834, 0xDD1E }; // U+1D11E + a.assignUtf(music16, 2); + lString32 b; + const lChar32 music32[] = { 0x1D11E }; + b.assignUtf(music32, 1); + TCHECK(a.compareUtf(b) == 0); + TCHECK(b.compareUtf(a) == 0); + } + + // --- compareUtf same pointer (pchunk equality) --- + { + lString8 a{"test"}; + TCHECK(a.compareUtf(a) == 0); + } +} + +void test_lstring2() { + printf("New strings library tests\n"); + test_lstring2_chunks(); + DUMP_ALLOC_STATS("before test_lstring8()") + test_lstring8(); + DUMP_ALLOC_STATS("after test_lstring8()") + test_writable_refs_lString8(); + test_lstring2_unicode(); + + if (test_errors == 0) { + printf("New strings library tests completed successfully\n"); + } else { + printf("***\nNEW STRINGS LIBRARY TESTS COMPLETED WITH %d ERRORS\n***\n", test_errors); + } + printf("New strings library completed\n"); +} + +} diff --git a/tests/src/lvstring2test.h b/tests/src/lvstring2test.h new file mode 100644 index 0000000000..8c9d5ddda3 --- /dev/null +++ b/tests/src/lvstring2test.h @@ -0,0 +1,13 @@ +#ifndef LVSTRING2TEST_H +#define LVSTRING2TEST_H + +#include "lvstring2.h" + +namespace lv { + +void test_lstring2(); +void test_lstring2_unicode(); + +} + +#endif // LVSTRING2TEST_H diff --git a/tests/src/stringtest.cpp b/tests/src/stringtest.cpp new file mode 100644 index 0000000000..b819e80fbf --- /dev/null +++ b/tests/src/stringtest.cpp @@ -0,0 +1,1250 @@ +#include +#include +#include "../crengine/include/crengine.h" +#include "../crengine/include/lvstring8collection.h" +#include "../crengine/include/lvstring32collection.h" +#include "stringtest.h" +#include "lvstring2test.h" + +#if (LDOM_USE_OWN_MEM_MAN==1) +#define CHECKBUF_(file, line) check_ls_storage(file ":" #line) +#define CHECKBUF CHECKBUF_(__FILE__, __LINE__) +#endif + +static int test_errors = 0; +#if (LDOM_USE_OWN_MEM_MAN==1) +#define TCHECK(cond) do { CHECKBUF; if (!(cond)) { printf("FAIL line %d: %s\n", __LINE__, #cond); test_errors++; } } while(0) +#else +#define TCHECK(cond) do { if (!(cond)) { printf("FAIL line %d: %s\n", __LINE__, #cond); test_errors++; } } while(0) +#endif + +class TempStringHolder8 { + public: + lString8 data; + TempStringHolder8(const lString8 & s) : data(s) { } + ~TempStringHolder8() = default; +}; + +void testStrings8_16() { + printf("=== MIX lString8 tests ===\n"); + + // Constructors + lString8 s8_default; + TCHECK(s8_default.empty()); + + lString8 s8_size(10); + TCHECK(s8_size.empty()); + + lString8 s8_cstr("hello"); + TCHECK(s8_cstr.length() == 5); + TCHECK(s8_cstr == "hello"); + + TempStringHolder8 s8_str_hld1(s8_cstr); + TempStringHolder8 s8_str_hld2(s8_cstr + "bla bla"); + TempStringHolder8 s8_str_hld3(s8_cstr + "bla bla" + s8_str_hld1.data); + + { + lString8 face((const char*)"serifExtraCondensed"); + lString8 style8((const char*)"italic"); + style8.lowercase(); + if (style8.pos("extracondensed") >= 0) + face << " ExtraCondensed"; + else if (style8.pos("semicondensed") >= 0) + face << " SemiCondensed"; + else if (style8.pos("condensed") >= 0) + face << " Condensed"; + TempStringHolder8 fontFace(face); + } + + + lString8 s8_fragment("hello world", 5); + TCHECK(s8_fragment == "hello"); + + lString8 s8_sub(s8_cstr, 1, 3); + TCHECK(s8_sub == "ell"); + + // Copy / move + lString8 s8_copy(s8_cstr); + TCHECK(s8_copy == "hello"); + lString8 s8_move(std::move(s8_copy)); + TCHECK(s8_move == "hello"); + + // Assignment + lString8 s8_assign; + s8_assign = s8_move; + TCHECK(s8_assign == "hello"); + lString8 s8_assign2; + s8_assign2 = "world"; + TCHECK(s8_assign2 == "world"); + lString8 s8_assign3; + s8_assign3 = lString8("temp"); + TCHECK(s8_assign3 == "temp"); + + // assign methods + lString8 s8_a; + s8_a.assign("test"); + TCHECK(s8_a == "test"); + s8_a.assign("abcdef", 3); + TCHECK(s8_a == "abc"); + lString8 s8_b("xyz"); + s8_a.assign(s8_b); + TCHECK(s8_a == "xyz"); + s8_a.assign(lString8("move_assign")); + TCHECK(s8_a == "move_assign"); + + // Append + lString8 s8_app; + s8_app.append("foo"); + TCHECK(s8_app == "foo"); + s8_app.append("xy", 1); + TCHECK(s8_app == "foox"); + s8_app.append(3, '!'); + TCHECK(s8_app == "foox!!!"); + s8_app.clear(); + + // appendDecimal / appendHex + s8_app.appendDecimal(12345); + TCHECK(s8_app == "12345"); + s8_app.clear(); + s8_app.appendDecimal(-42); + TCHECK(s8_app == "-42"); + s8_app.clear(); + s8_app.appendHex(0xFF); + TCHECK(s8_app == "ff"); + s8_app.clear(); + + // Insert + lString8 s8_ins("world"); + s8_ins.insert(0, 2, 'X'); + TCHECK(s8_ins == "XXworld"); + + // Replace + lString8 s8_rep("hello world"); + s8_rep.replace(0, 5, lString8("good")); + TCHECK(s8_rep == "good world"); + lString8 s8_rep2("abcabc"); + s8_rep2.replace('a', 'X'); + TCHECK(s8_rep2 == "XbcXbc"); + + // Erase + lString8 s8_era("0123456789"); + s8_era.erase(5, 5); + TCHECK(s8_era == "01234"); + + // uppercase / lowercase + lString8 s8_case("Hello World"); + s8_case.uppercase(); + TCHECK(s8_case == "HELLO WORLD"); + s8_case.lowercase(); + TCHECK(s8_case == "hello world"); + + // compare + TCHECK(s8_cstr.compare("hello") == 0); + TCHECK(s8_cstr.compare("world") < 0); + TCHECK(s8_cstr.compare("abc") > 0); + + // pos / rpos + lString8 s8_pos("hello world, hello"); + TCHECK(s8_pos.pos('w') == 6); + TCHECK(s8_pos.pos("world") == 6); + TCHECK(s8_pos.pos('h', 1) == 13); + TCHECK(s8_pos.rpos("hello") == 13); + TCHECK(s8_pos.pos(lString8("hello"), 1) == 13); + + // startsWith / endsWith + lString8 s8_sw("hello.cpp"); + TCHECK(s8_sw.startsWith("hello")); + TCHECK(s8_sw.startsWith(lString8("hello"))); + TCHECK(!s8_sw.startsWith("world")); + TCHECK(s8_sw.endsWith(".cpp")); + + // substr + lString8 s8_sub2("hello world"); + TCHECK(s8_sub2.substr(6) == "world"); + TCHECK(s8_sub2.substr(0, 5) == "hello"); + + // operator[] + TCHECK(s8_sub2[0] == 'h'); + TCHECK(s8_sub2[4] == 'o'); + + // data / c_str + TCHECK(strcmp(s8_sub2.c_str(), "hello world") == 0); + TCHECK(strcmp(s8_sub2.data(), "hello world") == 0); + + // length / size / capacity / empty + TCHECK(s8_sub2.length() == 11); + TCHECK(s8_sub2.size() == 11); + TCHECK(!s8_sub2.empty()); + lString8 s8_empty; + TCHECK(s8_empty.empty()); + TCHECK(!s8_empty); + + // at + TCHECK(s8_sub2.at(0) == 'h'); + TCHECK(s8_sub2.at(10) == 'd'); + + // atoi + lString8 s8_int("12345"); + TCHECK(s8_int.atoi() == 12345); + lString8 s8_int2("-999"); + TCHECK(s8_int2.atoi() == -999); + lString8 s8_int64("1234567890123"); + TCHECK(s8_int64.atoi64() == 1234567890123LL); + + // swap + lString8 s8_swap1("first"); + lString8 s8_swap2("second"); + s8_swap1.swap(s8_swap2); + TCHECK(s8_swap1 == "second"); + TCHECK(s8_swap2 == "first"); + + // trim + lString8 s8_trim(" spaced "); + s8_trim.trim(); + TCHECK(s8_trim == "spaced"); + + // pack + lString8 s8_pack("hello"); + s8_pack.reserve(1000); + s8_pack.pack(); + + // clear / reset + lString8 s8_clr("something"); + s8_clr.clear(); + TCHECK(s8_clr.empty()); + lString8 s8_rst("something"); + s8_rst.reset(50); + TCHECK(s8_rst.empty()); + TCHECK(s8_rst.capacity() >= 49); + + // resize + lString8 s8_rsz("hi"); + s8_rsz.resize(5, 'x'); + + // lastChar / firstChar + lString8 s8_lf("abc"); + TCHECK(s8_lf.firstChar() == 'a'); + TCHECK(s8_lf.lastChar() == 'c'); + + // getHash + lString8 s8_h1("hello"); + lString8 s8_h2("hello"); + TCHECK(s8_h1.getHash() == s8_h2.getHash()); + + // operator << + lString8 s8_shift; + s8_shift << 'A'; + TCHECK(s8_shift == "A"); + s8_shift << "BC"; + TCHECK(s8_shift == "ABC"); + s8_shift << lString8("DE"); + TCHECK(s8_shift == "ABCDE"); + s8_shift.clear(); + s8_shift << fmt::decimal(42); + TCHECK(s8_shift == "42"); + s8_shift.clear(); + s8_shift << fmt::hex(255); + TCHECK(s8_shift == "ff"); + + // operator += + lString8 s8_plus; + s8_plus += "hello"; + TCHECK(s8_plus == "hello"); + s8_plus += lString8(" world"); + TCHECK(s8_plus == "hello world"); + s8_plus += '!'; + TCHECK(s8_plus == "hello world!"); + s8_plus += fmt::decimal(99); + TCHECK(s8_plus == "hello world!99"); + + // external operator == / != + lString8 s8_eq("equal"); + lString8 s8_eq2("equal"); + lString8 s8_neq("not_equal"); + TCHECK(s8_eq == s8_eq2); + TCHECK(!(s8_eq != s8_eq2)); + TCHECK(s8_eq != s8_neq); + TCHECK(s8_eq == "equal"); + TCHECK("equal" == s8_eq); + TCHECK(s8_eq != "other"); + TCHECK("other" != s8_eq); + + // external operator + + lString8 s8_add("abc"); + lString8 s8_added = s8_add + "def"; + TCHECK(s8_added == "abcdef"); + s8_added = s8_add + lString8("ghi"); + TCHECK(s8_added == "abcghi"); + s8_added = s8_add + fmt::decimal(123); + TCHECK(s8_added == "abc123"); + s8_added = s8_add + fmt::hex(0xFF); + TCHECK(s8_added == "abcff"); + + // itoa static + lString8 s8_i = lString8::itoa(42); + TCHECK(s8_i == "42"); + s8_i = lString8::itoa(0U); + TCHECK(s8_i == "0"); + s8_i = lString8::itoa(-123LL); + TCHECK(s8_i == "-123"); + + // cs8 + const lString8& cs8_test = cs8("static_string"); + TCHECK(cs8_test == "static_string"); + const lString8& cs8_test2 = cs8("static_string"); + TCHECK(&cs8_test == &cs8_test2); + + printf("=== MIX lString16 tests ===\n"); + + // lString16 constructors + lString16 s16_default; + TCHECK(s16_default.empty()); + lString16 s16_cstr(u"hello"); + TCHECK(s16_cstr.length() == 5); + lString16 s16_from8("hello"); + TCHECK(s16_from8.length() == 5); + lString16 s16_frag(u"hello world", 5); + TCHECK(s16_frag == u"hello"); + lString16 s16_sub(s16_cstr, 1, 3); + TCHECK(s16_sub == u"ell"); + + // Copy / move + lString16 s16_copy(s16_cstr); + TCHECK(s16_copy == u"hello"); + lString16 s16_move(std::move(s16_copy)); + TCHECK(s16_move == u"hello"); + + // Assignment + lString16 s16_assign; + s16_assign = u"world"; + TCHECK(s16_assign == u"world"); + s16_assign = "ascii"; + TCHECK(s16_assign == u"ascii"); + + // Append + lString16 s16_app; + s16_app.append(u"foo"); + TCHECK(s16_app == u"foo"); + s16_app.append((const lChar8*)"bar"); + TCHECK(s16_app == u"foobar"); + s16_app.append(3, u'!'); + TCHECK(s16_app == u"foobar!!!"); + s16_app.clear(); + + // appendDecimal / appendHex + s16_app.appendDecimal(999); + TCHECK(s16_app == u"999"); + s16_app.clear(); + s16_app.appendHex(0xABC); + TCHECK(s16_app == u"abc"); + + // Insert + lString16 s16_ins(u"world"); + s16_ins.insert(0, 2, u'X'); + s16_ins.clear(); + s16_ins = u"world"; + s16_ins.insert(0, lString16(u"hello ")); + + // compare + TCHECK(s16_cstr.compare(u"hello") == 0); + TCHECK(s16_cstr.compare((const lChar8*)"hello") == 0); + TCHECK(s16_cstr.compare(u"world") < 0); + + // substr + lString16 s16_sub3(u"hello world"); + TCHECK(s16_sub3.substr(6) == u"world"); + TCHECK(s16_sub3.substr(0, 5) == u"hello"); + + // startsWith / endsWith + TCHECK(s16_sub3.startsWith(u"hello")); + TCHECK(s16_sub3.startsWith((const lChar8*)"hello")); + TCHECK(s16_sub3.endsWith(u"world")); + TCHECK(s16_sub3.endsWith((const lChar8*)"world")); + TCHECK(s16_sub3.endsWith(lString16(u"world"))); + + // operator[] + TCHECK(s16_sub3[0] == u'h'); + + // atoi + lString16 s16_int(u"6789"); + int s16_ival = 0; + lInt64 s16_ival64 = 0; + TCHECK(s16_int.atoi() == 6789); + TCHECK(s16_int.atoi(s16_ival) && s16_ival == 6789); + TCHECK(s16_int.atoi(s16_ival64) && s16_ival64 == 6789); + + // getHash + lString16 s16_h1(u"test"); + lString16 s16_h2(u"test"); + TCHECK(s16_h1.getHash() == s16_h2.getHash()); + + // trim / trimNonAlpha + lString16 s16_tr(u" spaced "); + s16_tr.trim(); + TCHECK(s16_tr == u"spaced"); + lString16 s16_tna(u"!!!hello!!!"); + s16_tna.trimNonAlpha(); + TCHECK(s16_tna == u"hello"); + + // swap + lString16 s16_sw1(u"first"); + lString16 s16_sw2(u"second"); + s16_sw1.swap(s16_sw2); + TCHECK(s16_sw1 == u"second"); + TCHECK(s16_sw2 == u"first"); + + // clear / reset + lString16 s16_clr(u"something"); + s16_clr.clear(); + TCHECK(s16_clr.empty()); + lString16 s16_rst2(u"something"); + s16_rst2.reset(50); + TCHECK(s16_rst2.empty()); + + // limit + lString16 s16_lim(u"hello world"); + s16_lim.limit(5); + TCHECK(s16_lim == u"hello"); + + // pack + lString16 s16_pk(u"test"); + s16_pk.reserve(500); + + // operator << + lString16 s16_sh; + s16_sh << u'A'; + TCHECK(s16_sh == u"A"); + s16_sh << u"BC"; + TCHECK(s16_sh == u"ABC"); + s16_sh << fmt::decimal(42); + TCHECK(s16_sh == u"ABC42"); + s16_sh.clear(); + s16_sh << fmt::hex(255); + TCHECK(s16_sh == u"ff"); + + // operator += + lString16 s16_pe; + s16_pe += u"hello"; + TCHECK(s16_pe == u"hello"); + s16_pe += lString16(u" world"); + TCHECK(s16_pe == u"hello world"); + + // itoa + lString16 s16_i16 = lString16::itoa(42); + TCHECK(s16_i16 == u"42"); + s16_i16 = lString16::itoa(0U); + TCHECK(s16_i16 == u"0"); + + // external operator == / != / + + lString16 s16_eq1(u"equal"); + lString16 s16_eq2(u"equal"); + TCHECK(s16_eq1 == s16_eq2); + TCHECK(s16_eq1 == u"equal"); + TCHECK(u"equal" == s16_eq1); + TCHECK(s16_eq1 != u"other"); + lString16 s16_added16 = s16_eq1 + u"_suffix"; + TCHECK(s16_added16 == u"equal_suffix"); + +} + +void testStrings8() { + + printf("=== lString8 tests ===\n"); + + // Constructors + lString8 s8_default; + TCHECK(s8_default.empty()); + + lString8 s8_size(10); + TCHECK(s8_size.empty()); + + lString8 s8_cstr("hello"); + TCHECK(s8_cstr.length() == 5); + TCHECK(s8_cstr == "hello"); + + lString8 s8_fragment("hello world", 5); + TCHECK(s8_fragment == "hello"); + + lString8 s8_sub(s8_cstr, 1, 3); + TCHECK(s8_sub == "ell"); + + // Copy / move + lString8 s8_copy(s8_cstr); + TCHECK(s8_copy == "hello"); + lString8 s8_move(std::move(s8_copy)); + TCHECK(s8_move == "hello"); + + // Assignment + lString8 s8_assign; + s8_assign = s8_move; + TCHECK(s8_assign == "hello"); + lString8 s8_assign2; + s8_assign2 = "world"; + TCHECK(s8_assign2 == "world"); + lString8 s8_assign3; + s8_assign3 = lString8("temp"); + TCHECK(s8_assign3 == "temp"); + + // assign methods + lString8 s8_a; + s8_a.assign("test"); + TCHECK(s8_a == "test"); + s8_a.assign("abcdef", 3); + TCHECK(s8_a == "abc"); + lString8 s8_b("xyz"); + s8_a.assign(s8_b); + TCHECK(s8_a == "xyz"); + s8_a.assign(lString8("move_assign")); + TCHECK(s8_a == "move_assign"); + + // Append + lString8 s8_app; + s8_app.append("foo"); + TCHECK(s8_app == "foo"); + s8_app.append("xy", 1); + TCHECK(s8_app == "foox"); + s8_app.append(3, '!'); + TCHECK(s8_app == "foox!!!"); + s8_app.clear(); + + // appendDecimal / appendHex + s8_app.appendDecimal(12345); + TCHECK(s8_app == "12345"); + s8_app.clear(); + s8_app.appendDecimal(-42); + TCHECK(s8_app == "-42"); + s8_app.clear(); + s8_app.appendHex(0xFF); + TCHECK(s8_app == "ff"); + s8_app.clear(); + + // Insert + lString8 s8_ins("world"); + s8_ins.insert(0, 2, 'X'); + TCHECK(s8_ins == "XXworld"); + + // Replace + lString8 s8_rep("hello world"); + s8_rep.replace(0, 5, lString8("good")); + TCHECK(s8_rep == "good world"); + lString8 s8_rep2("abcabc"); + s8_rep2.replace('a', 'X'); + TCHECK(s8_rep2 == "XbcXbc"); + + // Erase + lString8 s8_era("0123456789"); + s8_era.erase(5, 5); + TCHECK(s8_era == "01234"); + + // uppercase / lowercase + lString8 s8_case("Hello World"); + s8_case.uppercase(); + TCHECK(s8_case == "HELLO WORLD"); + s8_case.lowercase(); + TCHECK(s8_case == "hello world"); + + // compare + TCHECK(s8_cstr.compare("hello") == 0); + TCHECK(s8_cstr.compare("world") < 0); + TCHECK(s8_cstr.compare("abc") > 0); + + // pos / rpos + lString8 s8_pos("hello world, hello"); + TCHECK(s8_pos.pos('w') == 6); + TCHECK(s8_pos.pos("world") == 6); + TCHECK(s8_pos.pos('h', 1) == 13); + TCHECK(s8_pos.rpos("hello") == 13); + TCHECK(s8_pos.pos(lString8("hello"), 1) == 13); + + // startsWith / endsWith + lString8 s8_sw("hello.cpp"); + TCHECK(s8_sw.startsWith("hello")); + TCHECK(s8_sw.startsWith(lString8("hello"))); + TCHECK(!s8_sw.startsWith("world")); + TCHECK(s8_sw.endsWith(".cpp")); + + // substr + lString8 s8_sub2("hello world"); + TCHECK(s8_sub2.substr(6) == "world"); + TCHECK(s8_sub2.substr(0, 5) == "hello"); + + // operator[] + TCHECK(s8_sub2[0] == 'h'); + TCHECK(s8_sub2[4] == 'o'); + + // data / c_str + TCHECK(strcmp(s8_sub2.c_str(), "hello world") == 0); + TCHECK(strcmp(s8_sub2.data(), "hello world") == 0); + + // length / size / capacity / empty + TCHECK(s8_sub2.length() == 11); + TCHECK(s8_sub2.size() == 11); + TCHECK(!s8_sub2.empty()); + lString8 s8_empty; + TCHECK(s8_empty.empty()); + TCHECK(!s8_empty); + + // at + TCHECK(s8_sub2.at(0) == 'h'); + TCHECK(s8_sub2.at(10) == 'd'); + + // atoi + lString8 s8_int("12345"); + TCHECK(s8_int.atoi() == 12345); + lString8 s8_int2("-999"); + TCHECK(s8_int2.atoi() == -999); + lString8 s8_int64("1234567890123"); + TCHECK(s8_int64.atoi64() == 1234567890123LL); + + // swap + lString8 s8_swap1("first"); + lString8 s8_swap2("second"); + s8_swap1.swap(s8_swap2); + TCHECK(s8_swap1 == "second"); + TCHECK(s8_swap2 == "first"); + + // trim + lString8 s8_trim(" spaced "); + s8_trim.trim(); + TCHECK(s8_trim == "spaced"); + + // pack + lString8 s8_pack("hello"); + s8_pack.reserve(1000); + s8_pack.pack(); + + // clear / reset + lString8 s8_clr("something"); + s8_clr.clear(); + TCHECK(s8_clr.empty()); + lString8 s8_rst("something"); + s8_rst.reset(50); + TCHECK(s8_rst.empty()); + TCHECK(s8_rst.capacity() >= 49); + + // resize + lString8 s8_rsz("hi"); + s8_rsz.resize(5, 'x'); + + // lastChar / firstChar + lString8 s8_lf("abc"); + TCHECK(s8_lf.firstChar() == 'a'); + TCHECK(s8_lf.lastChar() == 'c'); + + // getHash + lString8 s8_h1("hello"); + lString8 s8_h2("hello"); + TCHECK(s8_h1.getHash() == s8_h2.getHash()); + + // operator << + lString8 s8_shift; + s8_shift << 'A'; + TCHECK(s8_shift == "A"); + s8_shift << "BC"; + TCHECK(s8_shift == "ABC"); + s8_shift << lString8("DE"); + TCHECK(s8_shift == "ABCDE"); + s8_shift.clear(); + s8_shift << fmt::decimal(42); + TCHECK(s8_shift == "42"); + s8_shift.clear(); + s8_shift << fmt::hex(255); + TCHECK(s8_shift == "ff"); + + // operator += + lString8 s8_plus; + s8_plus += "hello"; + TCHECK(s8_plus == "hello"); + s8_plus += lString8(" world"); + TCHECK(s8_plus == "hello world"); + s8_plus += '!'; + TCHECK(s8_plus == "hello world!"); + s8_plus += fmt::decimal(99); + TCHECK(s8_plus == "hello world!99"); + + // external operator == / != + lString8 s8_eq("equal"); + lString8 s8_eq2("equal"); + lString8 s8_neq("not_equal"); + TCHECK(s8_eq == s8_eq2); + TCHECK(!(s8_eq != s8_eq2)); + TCHECK(s8_eq != s8_neq); + TCHECK(s8_eq == "equal"); + TCHECK("equal" == s8_eq); + TCHECK(s8_eq != "other"); + TCHECK("other" != s8_eq); + + // external operator + + lString8 s8_add("abc"); + lString8 s8_added = s8_add + "def"; + TCHECK(s8_added == "abcdef"); + s8_added = s8_add + lString8("ghi"); + TCHECK(s8_added == "abcghi"); + s8_added = s8_add + fmt::decimal(123); + TCHECK(s8_added == "abc123"); + s8_added = s8_add + fmt::hex(0xFF); + TCHECK(s8_added == "abcff"); + + // itoa static + lString8 s8_i = lString8::itoa(42); + TCHECK(s8_i == "42"); + s8_i = lString8::itoa(0U); + TCHECK(s8_i == "0"); + s8_i = lString8::itoa(-123LL); + TCHECK(s8_i == "-123"); + + // cs8 + const lString8& cs8_test = cs8("static_string"); + TCHECK(cs8_test == "static_string"); + const lString8& cs8_test2 = cs8("static_string"); + TCHECK(&cs8_test == &cs8_test2); +} + +void testStrings16() { + printf("=== lString16 tests ===\n"); + + // lString16 constructors + lString16 s16_default; + TCHECK(s16_default.empty()); + lString16 s16_cstr(u"hello"); + TCHECK(s16_cstr.length() == 5); + lString16 s16_from8("hello"); + TCHECK(s16_from8.length() == 5); + lString16 s16_frag(u"hello world", 5); + TCHECK(s16_frag == u"hello"); + lString16 s16_sub(s16_cstr, 1, 3); + TCHECK(s16_sub == u"ell"); + + // Copy / move + lString16 s16_copy(s16_cstr); + TCHECK(s16_copy == u"hello"); + lString16 s16_move(std::move(s16_copy)); + TCHECK(s16_move == u"hello"); + + // Assignment + lString16 s16_assign; + s16_assign = u"world"; + TCHECK(s16_assign == u"world"); + s16_assign = "ascii"; + TCHECK(s16_assign == u"ascii"); + + // Append + lString16 s16_app; + s16_app.append(u"foo"); + TCHECK(s16_app == u"foo"); + s16_app.append((const lChar8*)"bar"); + TCHECK(s16_app == u"foobar"); + s16_app.append(3, u'!'); + TCHECK(s16_app == u"foobar!!!"); + s16_app.clear(); + + // appendDecimal / appendHex + s16_app.appendDecimal(999); + TCHECK(s16_app == u"999"); + s16_app.clear(); + s16_app.appendHex(0xABC); + TCHECK(s16_app == u"abc"); + + // Insert + lString16 s16_ins(u"world"); + s16_ins.insert(0, 2, u'X'); + s16_ins.clear(); + s16_ins = u"world"; + s16_ins.insert(0, lString16(u"hello ")); + + // compare + TCHECK(s16_cstr.compare(u"hello") == 0); + TCHECK(s16_cstr.compare((const lChar8*)"hello") == 0); + TCHECK(s16_cstr.compare(u"world") < 0); + + // substr + lString16 s16_sub3(u"hello world"); + TCHECK(s16_sub3.substr(6) == u"world"); + TCHECK(s16_sub3.substr(0, 5) == u"hello"); + + // startsWith / endsWith + TCHECK(s16_sub3.startsWith(u"hello")); + TCHECK(s16_sub3.startsWith((const lChar8*)"hello")); + TCHECK(s16_sub3.endsWith(u"world")); + TCHECK(s16_sub3.endsWith((const lChar8*)"world")); + TCHECK(s16_sub3.endsWith(lString16(u"world"))); + + // operator[] + TCHECK(s16_sub3[0] == u'h'); + + // atoi + lString16 s16_int(u"6789"); + int s16_ival = 0; + lInt64 s16_ival64 = 0; + TCHECK(s16_int.atoi() == 6789); + TCHECK(s16_int.atoi(s16_ival) && s16_ival == 6789); + TCHECK(s16_int.atoi(s16_ival64) && s16_ival64 == 6789); + + // getHash + lString16 s16_h1(u"test"); + lString16 s16_h2(u"test"); + TCHECK(s16_h1.getHash() == s16_h2.getHash()); + + // trim / trimNonAlpha + lString16 s16_tr(u" spaced "); + s16_tr.trim(); + TCHECK(s16_tr == u"spaced"); + lString16 s16_tna(u"!!!hello!!!"); + s16_tna.trimNonAlpha(); + TCHECK(s16_tna == u"hello"); + + // swap + lString16 s16_sw1(u"first"); + lString16 s16_sw2(u"second"); + s16_sw1.swap(s16_sw2); + TCHECK(s16_sw1 == u"second"); + TCHECK(s16_sw2 == u"first"); + + // clear / reset + lString16 s16_clr(u"something"); + s16_clr.clear(); + TCHECK(s16_clr.empty()); + lString16 s16_rst2(u"something"); + s16_rst2.reset(50); + TCHECK(s16_rst2.empty()); + + // limit + lString16 s16_lim(u"hello world"); + s16_lim.limit(5); + TCHECK(s16_lim == u"hello"); + + // pack + lString16 s16_pk(u"test"); + s16_pk.reserve(500); + + // operator << + lString16 s16_sh; + s16_sh << u'A'; + TCHECK(s16_sh == u"A"); + s16_sh << u"BC"; + TCHECK(s16_sh == u"ABC"); + s16_sh << fmt::decimal(42); + TCHECK(s16_sh == u"ABC42"); + s16_sh.clear(); + s16_sh << fmt::hex(255); + TCHECK(s16_sh == u"ff"); + + // operator += + lString16 s16_pe; + s16_pe += u"hello"; + TCHECK(s16_pe == u"hello"); + s16_pe += lString16(u" world"); + TCHECK(s16_pe == u"hello world"); + + // itoa + lString16 s16_i16 = lString16::itoa(42); + TCHECK(s16_i16 == u"42"); + s16_i16 = lString16::itoa(0U); + TCHECK(s16_i16 == u"0"); + + // external operator == / != / + + lString16 s16_eq1(u"equal"); + lString16 s16_eq2(u"equal"); + TCHECK(s16_eq1 == s16_eq2); + TCHECK(s16_eq1 == u"equal"); + TCHECK(u"equal" == s16_eq1); + TCHECK(s16_eq1 != u"other"); + lString16 s16_added16 = s16_eq1 + u"_suffix"; + TCHECK(s16_added16 == u"equal_suffix"); +} + +void testStrings32() { + printf("=== lString32 tests ===\n"); + + // lString32 constructors + lString32 s32_default; + TCHECK(s32_default.empty()); + lString32 s32_cstr(U"hello"); + TCHECK(s32_cstr.length() == 5); + lString32 s32_from8("hello"); + TCHECK(s32_from8.length() == 5); + lString32 s32_frag(U"hello world", 5); + TCHECK(s32_frag == U"hello"); + lString32 s32_sub(s32_cstr, 1, 3); + TCHECK(s32_sub == U"ell"); + + // Copy / move + lString32 s32_copy(s32_cstr); + TCHECK(s32_copy == U"hello"); + lString32 s32_move(std::move(s32_copy)); + TCHECK(s32_move == U"hello"); + + // Assignment + lString32 s32_assign; + s32_assign = U"world"; + TCHECK(s32_assign == U"world"); + s32_assign = "ascii"; + TCHECK(s32_assign == U"ascii"); + + // Append + lString32 s32_app; + s32_app.append(U"foo"); + TCHECK(s32_app == U"foo"); + s32_app.append((const lChar8*)"bar"); + TCHECK(s32_app == U"foobar"); + s32_app.append(3, U'!'); + TCHECK(s32_app == U"foobar!!!"); + s32_app.clear(); + s32_app.append(lString32(U"test")); + TCHECK(s32_app == U"test"); + + // appendDecimal / appendHex + s32_app.appendDecimal(12345); + TCHECK(s32_app == U"test12345"); + s32_app.clear(); + s32_app.appendHex(0xFF); + TCHECK(s32_app == U"ff"); + + // Insert + lString32 s32_ins(U"world"); + s32_ins.insert(0, lString32(U"hello ")); + TCHECK(s32_ins == U"hello world"); + + // Replace + lString32 s32_rp(U"ab_ab_ab"); + s32_rp.replace(U"ab", U"X"); + + // Replace (range) + lString32 s32_rpr(U"hello world"); + s32_rpr.replace(0, 5, lString32(U"good")); + + // Erase + lString32 s32_era(U"0123456789"); + s32_era.erase(5, 5); + TCHECK(s32_era == U"01234"); + + // uppercase / lowercase / capitalize + lString32 s32_case(U"hello world"); + s32_case.uppercase(); + TCHECK(s32_case == U"HELLO WORLD"); + s32_case.lowercase(); + TCHECK(s32_case == U"hello world"); + s32_case.capitalize(); + TCHECK(s32_case == U"Hello World"); + + // fullWidthChars + lString32 s32_fw(U"abc"); + s32_fw.fullWidthChars(); + TCHECK(s32_fw.length() == 3); + TCHECK(s32_fw[0] > 0xFF00); + + // compare + TCHECK(s32_cstr.compare(U"hello") == 0); + TCHECK(s32_cstr.compare((const lChar8*)"hello") == 0); + TCHECK(s32_cstr.compare(U"world") < 0); + + // split2 + lString32 s32_sp(U"key=value"); + lString32 k, v; + s32_sp.split2(U"=", k, v); + + // pos / rpos + lString32 s32_pos(U"hello world, hello"); + TCHECK(s32_pos.pos(U'w') == 6); + TCHECK(s32_pos.pos(U"world") == 6); + TCHECK(s32_pos.pos(U'h', 1) == 13); + TCHECK(s32_pos.rpos(U"hello") == 13); + + // startsWith / endsWith / startsWithNoCase + lString32 s32_sw(U"Hello.cpp"); + TCHECK(s32_sw.startsWith(U"Hello")); + TCHECK(s32_sw.startsWith((const lChar8*)"Hello")); + TCHECK(s32_sw.startsWith(lString32(U"Hello"))); + TCHECK(s32_sw.endsWith(U".cpp")); + TCHECK(s32_sw.endsWith((const lChar8*)".cpp")); + TCHECK(s32_sw.endsWith(lString32(U".cpp"))); + lString32 s32_nc(U"Hello World"); + TCHECK(s32_nc.startsWithNoCase(lString32(U"hello"))); + + // substr + lString32 s32_sub2(U"hello world"); + TCHECK(s32_sub2.substr(6) == U"world"); + TCHECK(s32_sub2.substr(0, 5) == U"hello"); + + // atoi / atod + lString32 s32_int(U"12345"); + TCHECK(s32_int.atoi() == 12345); + int _i; + lInt64 _i64; + double _d; + TCHECK(s32_int.atoi(_i) && _i == 12345); + TCHECK(s32_int.atoi(_i64) && _i64 == 12345); + lString32 s32_dbl(U"3.14"); + TCHECK(s32_dbl.atod() > 3.13 && s32_dbl.atod() < 3.15); + TCHECK(s32_dbl.atod(_d) && _d > 3.13 && _d < 3.15); + + // operator[] + TCHECK(s32_sub2[0] == U'h'); + + // data / c_str + lString32 s32_dc(U"test_data"); + TCHECK(s32_dc.c_str() != nullptr); + TCHECK(s32_dc.data() != nullptr); + + // getHash + lString32 s32_h1(U"hash"); + lString32 s32_h2(U"hash"); + TCHECK(s32_h1.getHash() == s32_h2.getHash()); + + // swap + lString32 s32_sw1(U"first"); + lString32 s32_sw2(U"second"); + s32_sw1.swap(s32_sw2); + TCHECK(s32_sw1 == U"second"); + TCHECK(s32_sw2 == U"first"); + + // clear / reset + lString32 s32_clr(U"something"); + s32_clr.clear(); + TCHECK(s32_clr.empty()); + lString32 s32_rst3(U"something"); + s32_rst3.reset(50); + TCHECK(s32_rst3.empty()); + + // resize / limit + lString32 s32_rsz(U"hi"); + s32_rsz.resize(5, U'x'); + TCHECK(s32_rsz == U"hixxx"); + lString32 s32_lim(U"hello world"); + s32_lim.limit(5); + TCHECK(s32_lim == U"hello"); + + // trim / trimNonAlpha / trimDoubleSpaces + lString32 s32_tr(U" spaced "); + s32_tr.trim(); + TCHECK(s32_tr == U"spaced"); + lString32 s32_tna(U"!!!hello!!!"); + s32_tna.trimNonAlpha(); + TCHECK(s32_tna == U"hello"); + lString32 s32_tds(U"a b c"); + s32_tds.trimDoubleSpaces(false, false); + TCHECK(s32_tds == U"a b c"); + + // pack + lString32 s32_pk2(U"test"); + s32_pk2.reserve(500); + TCHECK(s32_pk2.capacity() >= 500); + s32_pk2.pack(); + TCHECK(s32_pk2.capacity() < 100); + + // operator << + lString32 s32_sh; + s32_sh << U'A'; + TCHECK(s32_sh == U"A"); + s32_sh << U"BC"; + TCHECK(s32_sh == U"ABC"); + s32_sh << fmt::decimal(42); + TCHECK(s32_sh == U"ABC42"); + s32_sh.clear(); + s32_sh << fmt::hex(255); + TCHECK(s32_sh == U"ff"); + + // operator += + lString32 s32_pe; + s32_pe += U"hello"; + TCHECK(s32_pe == U"hello"); + s32_pe += lString32(U" world"); + TCHECK(s32_pe == U"hello world"); + + // replaceParam / replaceIntParam + lString32 s32_rp2(U"Hello $1, you have $2 messages"); + TCHECK(s32_rp2.replaceParam(1, U"Alice")); + TCHECK(s32_rp2.replaceIntParam(2, 5)); + TCHECK(s32_rp2 == U"Hello Alice, you have 5 messages"); + + // itoa + lString32 s32_i32 = lString32::itoa(42); + TCHECK(s32_i32 == U"42"); + s32_i32 = lString32::itoa(0U); + TCHECK(s32_i32 == U"0"); + s32_i32 = lString32::itoa(-99LL); + TCHECK(s32_i32 == U"-99"); + s32_i32 = lString32::itoa(0xFFFFFFFFFFFFFFFFULL); + TCHECK(!s32_i32.empty()); + + // cs32 + const lString32& cs32_test = cs32("static_str32"); + TCHECK(cs32_test == U"static_str32"); + const lString32& cs32_test2 = cs32(U"wide_static"); + TCHECK(cs32_test2 == U"wide_static"); + + // external operator == / != / + + lString32 s32_eq1(U"equal"); + lString32 s32_eq2(U"equal"); + TCHECK(s32_eq1 == s32_eq2); + TCHECK(s32_eq1 == U"equal"); + TCHECK(U"equal" == s32_eq1); + TCHECK(s32_eq1 != U"other"); + lString32 s32_added32 = s32_eq1 + U"_suffix"; + TCHECK(s32_added32 == U"equal_suffix"); + s32_added32 = s32_eq1 + lString32(U"_extra"); + TCHECK(s32_added32 == U"equal_extra"); + s32_added32 = s32_eq1 + fmt::decimal(123); + TCHECK(s32_added32 == U"equal123"); + s32_added32 = s32_eq1 + fmt::hex(0xFF); + TCHECK(s32_added32 == U"equalff"); + + // Mixed-type comparisons + lString32 s32_mix(U"test"); + lString16 s16_mix(u"test"); + lString8 s8_mix("test"); + TCHECK(s32_mix == s16_mix); + TCHECK(s32_mix == s8_mix); + TCHECK(s16_mix == s32_mix); + + // External conversion functions + lString32 s32_conv(U"Hello World"); + lString8 s8_utf8 = UnicodeToUtf8(s32_conv); + TCHECK(s8_utf8 == "Hello World"); + lString16 s16_utf16 = UnicodeToUtf16(s32_conv); + TCHECK(s16_utf16 == u"Hello World"); + lString8 s8_local = UnicodeToLocal(s32_conv); + TCHECK(!s8_local.empty()); + lString8 s8_wtf8 = UnicodeToWtf8(s32_conv); + TCHECK(s8_wtf8 == "Hello World"); + + // Reverse conversion + lString32 s32_back = Utf8ToUnicode(s8_utf8); + TCHECK(s32_back == U"Hello World"); + s32_back = Utf8ToUnicode("direct_cstr"); + TCHECK(s32_back == U"direct_cstr"); + s32_back = Utf8ToUnicode("fragment", 4); + TCHECK(s32_back == U"frag"); + s32_back = Utf16ToUnicode(s16_utf16); + TCHECK(s32_back == U"Hello World"); + s32_back = Utf16ToUnicode(u"direct16"); + TCHECK(s32_back == U"direct16"); + lString32 s32_wtf = Wtf8ToUnicode(s8_wtf8); + TCHECK(s32_wtf == U"Hello World"); + s32_wtf = Wtf8ToUnicode("wtf_direct"); + TCHECK(s32_wtf == U"wtf_direct"); + + // external getHash + lString32 s32_gh(U"hash_me"); + lString16 s16_gh(u"hash_me"); + lString8 s8_gh("hash_me"); + TCHECK(getHash(s32_gh) == s32_gh.getHash()); + TCHECK(getHash(s16_gh) == s16_gh.getHash()); + TCHECK(getHash(s8_gh) == s8_gh.getHash()); + +} + +void testStringsMixed() { + printf("testStringsMixed()\n"); + // --- Resize --- + lString8 s_rsz {"hi"}; + s_rsz.resize(5, 'x'); + TCHECK(s_rsz.length() == 5); + TCHECK(s_rsz[2] == 'x'); + TCHECK(s_rsz[3] == 'x'); + TCHECK(s_rsz[4] == 'x'); + + // --- Resize to smaller (truncates length) --- + lString8 s_rsz2 {"hello world"}; + s_rsz2.resize(3); + TCHECK(s_rsz2.length() == 3); + +} + +bool test_newstring_only = false; + +void testStrings() { + lv::test_lstring2(); + + if (test_newstring_only) { + return; + } + + printf("test classic lStringX\n"); + + testStrings8_16(); + testStrings8(); + testStrings16(); + testStrings32(); + testStringsMixed(); + + + if (test_errors == 0) + printf("All string tests passed!\n"); + else + printf("FAILED: %d test(s) failed!\n", test_errors); +} + +void testStringCollections() { + { + lString8Collection list; + { + lString8 s {"test_s_1"}; + TCHECK(s == "test_s_1"); + list.add(s); + TCHECK(s == "test_s_1"); + } + { + lString8 s {"test_s_2"}; + s << "_less"; + TCHECK(s == "test_s_2"); + list.add(s); + TCHECK(s == "test_s_2"); + TCHECK(s.pos("_s") != 0); + s << "_more"; + s << "_more"; + } + } + { + lString8Collection list; + { + lString8 s {"test_s_3"}; + TCHECK(s == "test_s_3"); + list.add(s); + TCHECK(s == "test_s_3"); + TCHECK(s.pos("_s") != 0); + } + { + lString8 s {"test_s_4"}; + TCHECK(s == "test_s_4"); + list.add(s); + TCHECK(s == "test_s_4"); + TCHECK(s.pos("_s") != 0); + } + } + { + lString32Collection list; + { + lString32 s {U"test_s_5"}; + TCHECK(s == "test_s_5"); + list.add(s); + TCHECK(s == "test_s_5"); + TCHECK(s.pos("_s") != 0); + } + lString32 s {U"test_s_5"}; + TCHECK(s == "test_s_5"); + list.add(s); + TCHECK(s.pos("_s") != 0); + TCHECK(s == "test_s_5"); + } + + for (int sz = 1; sz < 1000; sz++) { + lString8Collection list; + for (int i = 0; i < sz; i++) { + lString8 s {"String8"}; + lString8 s2 {"tail"}; + TCHECK(s.pos("ing") != 0); + list.add(s); + list.add(s + s2); + list.add(s + "tails"); + } + } + for (int sz = 1; sz < 1000; sz++) { + lString32Collection list; + for (int i = 0; i < sz; i++) { + lString32 s {U"String32"}; + TCHECK(s.pos("ing") != 0); + list.add(s + s); + } + } +} diff --git a/tests/src/stringtest.h b/tests/src/stringtest.h new file mode 100644 index 0000000000..a220161e52 --- /dev/null +++ b/tests/src/stringtest.h @@ -0,0 +1,7 @@ +#ifndef __STRINGTEST_H_INCLUDED__ +#define __STRINGTEST_H_INCLUDED__ + +void testStrings(); +void testStringCollections(); + +#endif diff --git a/tests/src/testmain.cpp b/tests/src/testmain.cpp new file mode 100644 index 0000000000..1e47ba297e --- /dev/null +++ b/tests/src/testmain.cpp @@ -0,0 +1,499 @@ +#include + +#include "../crengine/include/crengine.h" +#include "../crengine/include/cr3version.h" +#include "../crengine/include/lvstring8collection.h" +#include "../crengine/include/lvstring32collection.h" +#include "stringtest.h" +#include +#include +#include + +//using unique_ptr = std::unique_ptr; + +bool getDirectoryFiles(lString32 path, lString32Collection & files) +{ + int foundCount = 0; + LVContainerRef dir = LVOpenDirectory(path.c_str()); + if ( !dir.isNull() ) { + CRLog::trace("Checking directory %s", path.c_str() ); + for ( int i=0; i < dir->GetObjectCount(); i++ ) { + const LVContainerItemInfo * item = dir->GetObjectInfo(i); + lString32 fileName = item->GetName(); + //printf("file in directory: %s\n", UnicodeToLocal(fileName).c_str()); + //printf(" test(%s) ", fn.c_str() ); + if ( !item->IsContainer() ) { + //bool found = false; + lString32 lc = fileName; + lc.lowercase(); + if (lc.startsWith(".")) { + continue; + } + lString32 fn; + fn << path; + LVAppendPathDelimiter(fn); + fn << fileName; + foundCount++; + files.add( fn ); + } + } + } else { + printf("Cannot open directory %s\n", UnicodeToLocal(path).c_str()); + } + return foundCount > 0; +} + +#if (USE_FREETYPE==1) +bool getDirectoryFonts( lString32Collection & pathList, lString32Collection & ext, lString32Collection & fonts, bool absPath ) +{ + int foundCount = 0; + lString32 path; + for ( int di=0; diGetObjectCount(); i++ ) { + const LVContainerItemInfo * item = dir->GetObjectInfo(i); + lString32 fileName = item->GetName(); + lString8 fn = UnicodeToLocal(fileName); + //printf(" test(%s) ", fn.c_str() ); + if ( !item->IsContainer() ) { + bool found = false; + lString32 lc = fileName; + lc.lowercase(); + for ( int j=0; j 0; +} +#endif + +lUInt64 currentTimeMillis() { + // Get the current time from the system clock + auto now = std::chrono::system_clock::now(); + + // Convert the current time to time since epoch + auto duration = now.time_since_epoch(); + + // Convert duration to milliseconds + auto milliseconds + = std::chrono::duration_cast( + duration) + .count(); + return milliseconds; +} + +struct PerfCounts { + lString32 fname; + int loadCount; + lUInt64 loadTime; + int renderCount; + lUInt64 renderTime; + int pageCount; + PerfCounts(lString32 fn = lString32::empty_str) + : fname(fn), loadCount(0), loadTime(0), renderCount(0), renderTime(0), pageCount(0) + { + } + ~PerfCounts() = default; + void reset() { + loadCount = 0; + loadTime = 0; + renderCount = 0; + renderTime = 0; + pageCount = 0; + } + void dump() { + printf("%s\t%lld\t%lld\t%d\n", UnicodeToLocal(fname).c_str(), loadTime, renderTime, pageCount); + } +}; + +using perf_vector = std::vector>; + +bool benchmarkFile(PerfCounts& perf) { + lString32 fname = perf.fname; + std::unique_ptr _docview { new LVDocView() }; + _docview->setViewMode(LVDocViewMode::DVM_PAGES, 2); + _docview->setRenderProps(2000, 1000); + _docview->setFontSize(30); +// _docview->up + + lUInt64 loadStart = currentTimeMillis(); + if (!_docview->LoadDocument(fname.c_str(), false)) { + return false; + } + perf.loadTime = currentTimeMillis() - loadStart; + perf.loadCount++; + lUInt64 renderStart = currentTimeMillis(); + _docview->Render(2000, 1000); + perf.renderTime = currentTimeMillis() - renderStart; + perf.pageCount += _docview->getPageCount(); + return true; +} + +void executeBenchmark(perf_vector& perfStats) { + for (auto &p : perfStats) { + printf("Opening file %s\n", UnicodeToLocal(p->fname).c_str()); + bool res = benchmarkFile(*p); + + if (!res) { + printf("Failed to open %s\n", UnicodeToLocal(p->fname).c_str()); + } + } +} + +void executeBenchmark(lString32Collection & testFiles) { + perf_vector perfStats; + for (int i = 0; i < testFiles.length(); i++) { + auto p = std::make_unique(testFiles[i]); + perfStats.push_back(std::move(p)); + } + + printf("Starting benchmark\n"); + executeBenchmark(perfStats); + + printf("Performance stats:\n"); + for (auto &p : perfStats) { + p->dump(); + } +} + +bool InitCREngine( const char * exename, lString32Collection & fontDirs ) +{ + CRLog::trace("InitCREngine(%s)", exename); +#ifdef _WIN32 + lString32 appname( exename ); + int lastSlash=-1; + lChar32 slashChar = '/'; + for ( int p=0; p<(int)appname.length(); p++ ) { + if ( appname[p]=='\\' ) { + slashChar = '\\'; + lastSlash = p; + } else if ( appname[p]=='/' ) { + slashChar = '/'; + lastSlash=p; + } + } + + lString32 appPath; + if ( lastSlash>=0 ) + appPath = appname.substr( 0, lastSlash+1 ); + InitCREngineLog(UnicodeToUtf8(appPath).c_str()); + lString32 datadir = appPath; +#else + lString32 datadir = lString32(CR3_DATA_DIR); +#endif + lString32 fontDir = datadir + "fonts"; + lString8 fontDir8_ = UnicodeToUtf8(fontDir); + + fontDirs.add( fontDir ); + + LVAppendPathDelimiter( fontDir ); + + lString8 fontDir8 = UnicodeToLocal(fontDir); + //const char * fontDir8s = fontDir8.c_str(); + //InitFontManager( fontDir8 ); + InitFontManager(lString8::empty_str); + +#if defined(_WIN32) && USE_FONTCONFIG!=1 + wchar_t sysdir_w[MAX_PATH+1]; + GetWindowsDirectoryW(sysdir_w, MAX_PATH); + lString32 fontdir = Utf16ToUnicode( sysdir_w ); + fontdir << "\\Fonts\\"; + lString8 fontdir8( UnicodeToUtf8(fontdir) ); + const char * fontnames[] = { + "arial.ttf", + "ariali.ttf", + "arialb.ttf", + "arialbi.ttf", + "arialn.ttf", + "arialni.ttf", + "arialnb.ttf", + "arialnbi.ttf", + "cour.ttf", + "couri.ttf", + "courbd.ttf", + "courbi.ttf", + "times.ttf", + "timesi.ttf", + "timesb.ttf", + "timesbi.ttf", + "comic.ttf", + "comicbd.ttf", + "verdana.ttf", + "verdanai.ttf", + "verdanab.ttf", + "verdanaz.ttf", + "bookos.ttf", + "bookosi.ttf", + "bookosb.ttf", + "bookosbi.ttf", + "calibri.ttf", + "calibrii.ttf", + "calibrib.ttf", + "calibriz.ttf", + "cambria.ttf", + "cambriai.ttf", + "cambriab.ttf", + "cambriaz.ttf", + "georgia.ttf", + "georgiai.ttf", + "georgiab.ttf", + "georgiaz.ttf", + NULL + }; + for ( int fi = 0; fontnames[fi]; fi++ ) { + fontMan->RegisterFont( fontdir8 + fontnames[fi] ); + } +#endif // defined(_WIN32) && USE_FONTCONFIG!=1 + // Load font definitions into font manager + // fonts are in files font1.lbf, font2.lbf, ... font32.lbf + // use fontconfig + +#if USE_FREETYPE==1 + lString32Collection fontExt; + fontExt.add(cs32(".ttf")); + fontExt.add(cs32(".otf")); + fontExt.add(cs32(".pfa")); + fontExt.add(cs32(".pfb")); + lString32Collection fonts; + + getDirectoryFonts( fontDirs, fontExt, fonts, true ); + + // load fonts from file + CRLog::debug("%d font files found", fonts.length()); + //if (!fontMan->GetFontCount()) { + for ( int fi=0; fiRegisterFont(fn) ) { + CRLog::trace(" failed\n"); + } + } + //} +#endif // USE_FREETYPE==1 + + // init hyphenation manager + //char hyphfn[1024]; + //sprintf(hyphfn, "Russian_EnUS_hyphen_(Alan).pdb" ); + //if ( !initHyph( (UnicodeToLocal(appPath) + hyphfn).c_str() ) ) { +#ifdef _LINUX + // initHyph( "/usr/share/crengine/hyph/Russian_EnUS_hyphen_(Alan).pdb" ); +#endif + //} + + if (!fontMan->GetFontCount()) + { + //error +#if (USE_FREETYPE==1) + printf("Fatal Error: Cannot open font file(s) .ttf \nCannot work without font\n" ); +#else + printf("Fatal Error: Cannot open font file(s) font#.lbf \nCannot work without font\nUse FontConv utility to generate .lbf fonts from TTF\n" ); +#endif + return false; + } + + printf("%d fonts loaded.\n", fontMan->GetFontCount()); + + return true; + +} + +void InitCREngineLog( const char * cfgfile ) +{ + if ( !cfgfile ) { + CRLog::setStdoutLogger(); + CRLog::setLogLevel( CRLog::LL_TRACE ); + return; + } + lString32 logfname; + lString32 loglevelstr = +#ifdef _DEBUG + U"TRACE"; +#else + U"TRACE"; +#endif + bool autoFlush = false; + CRPropRef logprops = LVCreatePropsContainer(); + { + LVStreamRef cfg = LVOpenFileStream( cfgfile, LVOM_READ ); + if ( !cfg.isNull() ) { + logprops->loadFromStream( cfg.get() ); + logfname = logprops->getStringDef( PROP_LOG_FILENAME, "stdout" ); + loglevelstr = logprops->getStringDef( PROP_LOG_LEVEL, "TRACE" ); + autoFlush = logprops->getBoolDef( PROP_LOG_AUTOFLUSH, false ); + } + } + CRLog::log_level level = CRLog::LL_INFO; + if (loglevelstr == "OFF") { + level = CRLog::LL_FATAL; + logfname.clear(); + } else if (loglevelstr == "FATAL") { + level = CRLog::LL_FATAL; + } else if (loglevelstr == "ERROR") { + level = CRLog::LL_ERROR; + } else if (loglevelstr == "WARN") { + level = CRLog::LL_WARN; + } else if (loglevelstr == "INFO") { + level = CRLog::LL_INFO; + } else if (loglevelstr == "DEBUG") { + level = CRLog::LL_DEBUG; + } else if (loglevelstr == "TRACE") { + level = CRLog::LL_TRACE; + } + if ( !logfname.empty() ) { + if (logfname == "stdout") + CRLog::setStdoutLogger(); + else if (logfname == "stderr") + CRLog::setStderrLogger(); + else + CRLog::setFileLogger( UnicodeToUtf8( logfname ).c_str(), autoFlush ); + } + CRLog::setLogLevel( level ); + CRLog::trace("Log initialization done."); +} + +void ShutdownCREngine() +{ + CRLog::info("Shutting down CREngine..."); + HyphMan::uninit(); + ShutdownFontManager(); + CRLog::setLogger( NULL ); +#if LDOM_USE_OWN_MEM_MAN == 1 +// ldomFreeStorage(); +#endif +} + +bool testsOnly = true; + +void runLibraryTestSuite() { + printf("String Tests\n"); + testStrings(); + testStringCollections(); + printf("CR3 Library Tests\n"); +} + +int main(int argc, const char ** argv) { + +#if (LDOM_USE_OWN_MEM_MAN == 1) + printf("LDOM_USE_OWN_MEM_MAN is turned ON\n"); +#endif + runLibraryTestSuite(); + if (testsOnly) { +#if (LDOM_USE_OWN_MEM_MAN == 1) + free_ls_storage(); +#endif + printf("CR3 Library Tests done. Exiting.\n"); + return 0; + } + + printf("CR3 Performance Tests\n"); + + lString32Collection files; + lString8 bookDir("../tests/testdata"); + for (int i = 1; i < argc; i++) { + if (argv[i][0]=='-') { + printf("option: %s\n", argv[i]); + } else { + bookDir = argv[i]; + if (!getDirectoryFiles(LocalToUnicode(bookDir), files)) { + printf("No book files found in directory %s\n", bookDir.c_str()); + return 3; + } + } + } + //LVAppendPathDelimiter(bookDir); + printf("Book dir: %s\n", bookDir.c_str()); +#ifdef DEBUG + lString8 loglevel("TRACE"); + lString8 logfile("stdout"); +#else + lString8 loglevel("ERROR"); + lString8 logfile("stderr"); +#endif + + // set logger + if ( logfile=="stdout" ) + CRLog::setStdoutLogger(); + else if ( logfile=="stderr" ) + CRLog::setStderrLogger(); + else if ( !logfile.empty() ) + CRLog::setFileLogger(logfile.c_str()); + if ( loglevel=="TRACE" ) + CRLog::setLogLevel(CRLog::LL_TRACE); + else if ( loglevel=="DEBUG" ) + CRLog::setLogLevel(CRLog::LL_DEBUG); + else if ( loglevel=="INFO" ) + CRLog::setLogLevel(CRLog::LL_INFO); + else if ( loglevel=="WARN" ) + CRLog::setLogLevel(CRLog::LL_WARN); + else if ( loglevel=="ERROR" ) + CRLog::setLogLevel(CRLog::LL_ERROR); + else + CRLog::setLogLevel(CRLog::LL_FATAL); + + lString32 exename = LocalToUnicode( lString8(argv[0]) ); + lString32 exedir = LVExtractPath(exename); + lString32 datadir = lString32(CR3_DATA_DIR); + LVAppendPathDelimiter(exedir); + LVAppendPathDelimiter(datadir); + lString32 exefontpath = exedir + "fonts"; + CRLog::info("main()"); + lString32Collection fontDirs; + + lString32 home = Utf8ToUnicode(lString8(( getenv("HOME") ) )); + lString32 homecr3 = home; + LVAppendPathDelimiter(homecr3); + homecr3 << ".cr3"; + LVAppendPathDelimiter(homecr3); + //~/.cr3/ + lString32 homefonts = homecr3; + homefonts << "fonts"; + + //fontDirs.add( cs32("/usr/local/share/crengine/fonts") ); + //fontDirs.add( cs32("/usr/local/share/fonts/truetype/freefont") ); + //fontDirs.add( cs32("/mnt/fonts") ); + fontDirs.add(homefonts); + + if ( !InitCREngine( argv[0], fontDirs ) ) { + printf("Cannot init CREngine - exiting\n"); + return 2; + } + + // use cache in current directory + if (!ldomDocCache::init( lString32(".cr3/cache"), 0x100000 * 256 )) { + printf("Cannot init doc cache!\n"); + } else { + ldomDocCache::clear(); + } + + + for (int i = 0; i < files.length(); i++) { + printf("test file [%d]: %s\n", i, UnicodeToLocal(files[i]).c_str()); + } + + printf("Starting benchmark...\n"); + executeBenchmark(files); + + ShutdownCREngine(); + printf("Exiting...\n"); + return 0; +} diff --git a/tests/testdata/.keep b/tests/testdata/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/thirdparty_unman/antiword/CMakeLists.txt b/thirdparty_unman/antiword/CMakeLists.txt index 5b27e75eb5..4fa5444986 100644 --- a/thirdparty_unman/antiword/CMakeLists.txt +++ b/thirdparty_unman/antiword/CMakeLists.txt @@ -6,6 +6,8 @@ ENDIF(NOT CMAKE_BUILD_TYPE) IF (${CMAKE_BUILD_TYPE} STREQUAL Release) ADD_DEFINITIONS( -DNDEBUG=1 ) +ELSEIF (${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) + ADD_DEFINITIONS( -DNDEBUG=1 ) ELSE() ADD_DEFINITIONS( -DDEBUG=1 ) ENDIF(${CMAKE_BUILD_TYPE} STREQUAL Release) diff --git a/thirdparty_unman/chmlib/CMakeLists.txt b/thirdparty_unman/chmlib/CMakeLists.txt index fce7510001..4ea5b6edd1 100644 --- a/thirdparty_unman/chmlib/CMakeLists.txt +++ b/thirdparty_unman/chmlib/CMakeLists.txt @@ -6,6 +6,8 @@ ENDIF(NOT CMAKE_BUILD_TYPE) IF (${CMAKE_BUILD_TYPE} STREQUAL Release) ADD_DEFINITIONS( -DNDEBUG=1 ) +ELSEIF (${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) + ADD_DEFINITIONS( -DNDEBUG=1 ) ELSE() ADD_DEFINITIONS( -DDEBUG=1 ) ENDIF(${CMAKE_BUILD_TYPE} STREQUAL Release)