From 79eb5f1f76f3eab8781ebc015cffe57bb4adc9a1 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 30 Nov 2024 14:13:30 -0500 Subject: [PATCH] strings done --- project/components/interface.cpp | 2 +- project/dependencies/containers.hpp | 28 +- project/dependencies/strings.cpp | 92 +-- project/dependencies/strings.hpp | 885 ++++++++++++++++------------ 4 files changed, 518 insertions(+), 489 deletions(-) diff --git a/project/components/interface.cpp b/project/components/interface.cpp index 134af0e..6a13b62 100644 --- a/project/components/interface.cpp +++ b/project/components/interface.cpp @@ -372,7 +372,7 @@ AllocatorInfo get_string_allocator( s32 str_length ) { Arena* last = & StringArenas.back(); - usize size_req = str_length + sizeof(String::Header) + sizeof(char*); + usize size_req = str_length + sizeof(StringHeader) + sizeof(char*); if ( last->TotalUsed + ssize(size_req) > last->TotalSize ) { diff --git a/project/dependencies/containers.hpp b/project/dependencies/containers.hpp index ecea1da..d185b24 100644 --- a/project/dependencies/containers.hpp +++ b/project/dependencies/containers.hpp @@ -376,26 +376,26 @@ struct HashTable { static constexpr f32 CriticalLoadScale = 0.7f; - Array Hashes; + Array Hashes; Array> Entries; #if 1 #pragma region Member Mapping - forceinline static HashTable init(AllocatorInfo allocator) { return GEN_NS hashtable_init(allocator); } + forceinline static HashTable init(AllocatorInfo allocator) { return GEN_NS hashtable_init(allocator); } forceinline static HashTable init_reserve(AllocatorInfo allocator, usize num) { return GEN_NS hashtable_init_reserve(allocator, num); } - forceinline void clear() { GEN_NS clear(*this); } - forceinline void destroy() { GEN_NS destroy(*this); } - forceinline Type* get(u64 key) { return GEN_NS get(*this, key); } - forceinline void grow() { GEN_NS grow(*this); } - forceinline void rehash(ssize new_num) { GEN_NS rehash(*this, new_num); } - forceinline void rehash_fast() { GEN_NS rehash_fast(*this); } - forceinline void remove(u64 key) { GEN_NS remove(*this, key); } - forceinline void remove_entry(ssize idx) { GEN_NS remove_entry(*this, idx); } - forceinline void set(u64 key, Type value) { GEN_NS set(*this, key, value); } - forceinline ssize slot(u64 key) { return GEN_NS slot(*this, key); } - forceinline void map(void (*proc)(u64, Type)) { GEN_NS map(*this, proc); } - forceinline void map_mut(void (*proc)(u64, Type*)) { GEN_NS map_mut(*this, proc); } + forceinline void clear() { GEN_NS clear(*this); } + forceinline void destroy() { GEN_NS destroy(*this); } + forceinline Type* get(u64 key) { return GEN_NS get(*this, key); } + forceinline void grow() { GEN_NS grow(*this); } + forceinline void rehash(ssize new_num) { GEN_NS rehash(*this, new_num); } + forceinline void rehash_fast() { GEN_NS rehash_fast(*this); } + forceinline void remove(u64 key) { GEN_NS remove(*this, key); } + forceinline void remove_entry(ssize idx) { GEN_NS remove_entry(*this, idx); } + forceinline void set(u64 key, Type value) { GEN_NS set(*this, key, value); } + forceinline ssize slot(u64 key) { return GEN_NS slot(*this, key); } + forceinline void map(void (*proc)(u64, Type)) { GEN_NS map(*this, proc); } + forceinline void map_mut(void (*proc)(u64, Type*)) { GEN_NS map_mut(*this, proc); } #pragma endregion Member Mapping #endif }; diff --git a/project/dependencies/strings.cpp b/project/dependencies/strings.cpp index a677122..209ee47 100644 --- a/project/dependencies/strings.cpp +++ b/project/dependencies/strings.cpp @@ -4,20 +4,9 @@ #endif #pragma region String - -String String::fmt( AllocatorInfo allocator, char* buf, ssize buf_size, char const* fmt, ... ) +String string_make_length( AllocatorInfo allocator, char const* str, ssize length ) { - va_list va; - va_start( va, fmt ); - str_fmt_va( buf, buf_size, fmt, va ); - va_end( va ); - - return make( allocator, buf ); -} - -String String::make_length( AllocatorInfo allocator, char const* str, ssize length ) -{ - constexpr ssize header_size = sizeof( Header ); + constexpr ssize header_size = sizeof( StringHeader ); s32 alloc_size = header_size + length + 1; void* allocation = alloc( allocator, alloc_size ); @@ -25,8 +14,8 @@ String String::make_length( AllocatorInfo allocator, char const* str, ssize leng if ( allocation == nullptr ) return { nullptr }; - Header& - header = * rcast(Header*, allocation); + StringHeader& + header = * rcast(StringHeader*, allocation); header = { allocator, length, length }; String result = { rcast( char*, allocation) + header_size }; @@ -41,9 +30,9 @@ String String::make_length( AllocatorInfo allocator, char const* str, ssize leng return result; } -String String::make_reserve( AllocatorInfo allocator, ssize capacity ) +String string_make_reserve( AllocatorInfo allocator, ssize capacity ) { - constexpr ssize header_size = sizeof( Header ); + constexpr ssize header_size = sizeof( StringHeader ); s32 alloc_size = header_size + capacity + 1; void* allocation = alloc( allocator, alloc_size ); @@ -53,8 +42,8 @@ String String::make_reserve( AllocatorInfo allocator, ssize capacity ) mem_set( allocation, 0, alloc_size ); - Header* - header = rcast(Header*, allocation); + StringHeader* + header = rcast(StringHeader*, allocation); header->Allocator = allocator; header->Capacity = capacity; header->Length = 0; @@ -62,69 +51,4 @@ String String::make_reserve( AllocatorInfo allocator, ssize capacity ) String result = { rcast(char*, allocation) + header_size }; return result; } - -String String::fmt_buf( AllocatorInfo allocator, char const* fmt, ... ) -{ - local_persist thread_local - char buf[ GEN_PRINTF_MAXLEN ] = { 0 }; - - va_list va; - va_start( va, fmt ); - str_fmt_va( buf, GEN_PRINTF_MAXLEN, fmt, va ); - va_end( va ); - - return make( allocator, buf ); -} - -bool String::append_fmt( char const* fmt, ... ) -{ - ssize res; - char buf[ GEN_PRINTF_MAXLEN ] = { 0 }; - - va_list va; - va_start( va, fmt ); - res = str_fmt_va( buf, count_of( buf ) - 1, fmt, va ) - 1; - va_end( va ); - - return append( buf, res ); -} - -bool String::make_space_for( char const* str, ssize add_len ) -{ - ssize available = avail_space(); - - // NOTE: Return if there is enough space left - if ( available >= add_len ) - { - return true; - } - else - { - ssize new_len, old_size, new_size; - - void* ptr; - void* new_ptr; - - AllocatorInfo allocator = get_header().Allocator; - Header* header = nullptr; - - new_len = grow_formula( length() + add_len ); - ptr = & get_header(); - old_size = size_of( Header ) + length() + 1; - new_size = size_of( Header ) + new_len + 1; - - new_ptr = resize( allocator, ptr, old_size, new_size ); - - if ( new_ptr == nullptr ) - return false; - - header = rcast( Header*, new_ptr); - header->Allocator = allocator; - header->Capacity = new_len; - - Data = rcast( char*, header + 1 ); - - return true; - } -} #pragma endregion String diff --git a/project/dependencies/strings.hpp b/project/dependencies/strings.hpp index 0db7d54..487ede5 100644 --- a/project/dependencies/strings.hpp +++ b/project/dependencies/strings.hpp @@ -19,8 +19,7 @@ struct StrC #define txt( text ) StrC { sizeof( text ) - 1, ( text ) } inline -StrC to_str( char const* str ) -{ +StrC to_str( char const* str ) { return { str_len( str ), str }; } @@ -28,417 +27,523 @@ StrC to_str( char const* str ) // This is directly based off the ZPL string api. // They used a header pattern // I kept it for simplicty of porting but its not necessary to keep it that way. +#pragma region String +struct String; +struct StringHeader; + +// Forward declarations for all file-scope functions +String string_make(AllocatorInfo allocator, char const* str); +String string_make(AllocatorInfo allocator, StrC str); +String string_make_reserve(AllocatorInfo allocator, ssize capacity); +String string_make_length(AllocatorInfo allocator, char const* str, ssize length); +String string_fmt(AllocatorInfo allocator, char* buf, ssize buf_size, char const* fmt, ...); +String string_fmt_buf(AllocatorInfo allocator, char const* fmt, ...); +String string_join(AllocatorInfo allocator, char const** parts, ssize num_parts, char const* glue); +usize string_grow_formula(usize value); +bool are_equal(String lhs, String rhs); +bool are_equal(String lhs, StrC rhs); +bool make_space_for(String& str, char const* to_append, ssize add_len); +bool append(String& str, char c); +bool append(String& str, char const* str_to_append); +bool append(String& str, char const* str_to_append, ssize length); +bool append(String& str, StrC str_to_append); +bool append(String& str, const String other); +bool append_fmt(String& str, char const* fmt, ...); +ssize avail_space(String const& str); +char& back(String& str); +bool contains(String const& str, StrC substring); +bool contains(String const& str, String const& substring); +ssize capacity(String const& str); +void clear(String& str); +String duplicate(String const& str, AllocatorInfo allocator); +void free(String& str); +StringHeader& get_header(String& str); +ssize length(String const& str); +b32 starts_with(String const& str, StrC substring); +b32 starts_with(String const& str, String substring); +void skip_line(String& str); +void strip_space(String& str); +void trim(String& str, char const* cut_set); +void trim_space(String& str); +String visualize_whitespace(String const& str); + +struct StringHeader { + AllocatorInfo Allocator; + ssize Capacity; + ssize Length; +}; + struct String { - struct Header - { - AllocatorInfo Allocator; - ssize Capacity; - ssize Length; - }; + char* Data; + +#if 1 +#pragma region Member Mapping + forceinline static String make(AllocatorInfo allocator, char const* str) { return GEN_NS string_make(allocator, str); } + forceinline static String make(AllocatorInfo allocator, StrC str) { return GEN_NS string_make(allocator, str); } + forceinline static String make_reserve(AllocatorInfo allocator, ssize cap) { return GEN_NS string_make_reserve(allocator, cap); } + forceinline static String make_length(AllocatorInfo a, char const* s, ssize l) { return GEN_NS string_make_length(a, s, l); } + forceinline static String join(AllocatorInfo a, char const** p, ssize n, char const* g) { return GEN_NS string_join(a, p, n, g); } + forceinline static usize grow_formula(usize value) { return GEN_NS string_grow_formula(value); } + forceinline static bool are_equal(String lhs, String rhs) { return GEN_NS are_equal(lhs, rhs); } + forceinline static bool are_equal(String lhs, StrC rhs) { return GEN_NS are_equal(lhs, rhs); } static - usize grow_formula( usize value ) - { - // Using a very aggressive growth formula to reduce time mem_copying with recursive calls to append in this library. - return 4 * value + 8; + String fmt(AllocatorInfo allocator, char* buf, ssize buf_size, char const* fmt, ...) { + va_list va; + va_start(va, fmt); + str_fmt_va(buf, buf_size, fmt, va); + va_end(va); + return GEN_NS string_make(allocator, buf); } static - String make( AllocatorInfo allocator, char const* str ) - { - ssize length = str ? str_len( str ) : 0; - return make_length( allocator, str, length ); + String fmt_buf(AllocatorInfo allocator, char const* fmt, ...) { + local_persist thread_local + char buf[GEN_PRINTF_MAXLEN] = { 0 }; + va_list va; + va_start(va, fmt); + str_fmt_va(buf, GEN_PRINTF_MAXLEN, fmt, va); + va_end(va); + return GEN_NS string_make(allocator, buf); } - static - String make( AllocatorInfo allocator, StrC str ) - { - return make_length( allocator, str.Ptr, str.Len ); + forceinline bool make_space_for(char const* str, ssize add_len) { return GEN_NS make_space_for(*this, str, add_len); } + forceinline bool append(char c) { return GEN_NS append(*this, c); } + forceinline bool append(char const* str) { return GEN_NS append(*this, str); } + forceinline bool append(char const* str, ssize length) { return GEN_NS append(*this, str, length); } + forceinline bool append(StrC str) { return GEN_NS append(*this, str); } + forceinline bool append(const String other) { return GEN_NS append(*this, other); } + forceinline ssize avail_space() const { return GEN_NS avail_space(*this); } + forceinline char& back() { return GEN_NS back(*this); } + forceinline bool contains(StrC substring) const { return GEN_NS contains(*this, substring); } + forceinline bool contains(String const& substring) const { return GEN_NS contains(*this, substring); } + forceinline ssize capacity() const { return GEN_NS capacity(*this); } + forceinline void clear() { GEN_NS clear(*this); } + forceinline String duplicate(AllocatorInfo allocator) const { return GEN_NS duplicate(*this, allocator); } + forceinline void free() { GEN_NS free(*this); } + forceinline ssize length() const { return GEN_NS length(*this); } + forceinline b32 starts_with(StrC substring) const { return GEN_NS starts_with(*this, substring); } + forceinline b32 starts_with(String substring) const { return GEN_NS starts_with(*this, substring); } + forceinline void skip_line() { GEN_NS skip_line(*this); } + forceinline void strip_space() { GEN_NS strip_space(*this); } + forceinline void trim(char const* cut_set) { GEN_NS trim(*this, cut_set); } + forceinline void trim_space() { GEN_NS trim_space(*this); } + forceinline String visualize_whitespace() const { return GEN_NS visualize_whitespace(*this); } + forceinline StringHeader& get_header() { return GEN_NS get_header(*this); } + + bool append_fmt(char const* fmt, ...) { + ssize res; + char buf[GEN_PRINTF_MAXLEN] = { 0 }; + + va_list va; + va_start(va, fmt); + res = str_fmt_va(buf, count_of(buf) - 1, fmt, va) - 1; + va_end(va); + + return GEN_NS append(*this, buf, res); } - static - String make_reserve( AllocatorInfo allocator, ssize capacity ); + forceinline operator bool() { return Data != nullptr; } + forceinline operator char*() { return Data; } + forceinline operator char const*() const { return Data; } + forceinline operator StrC() const { return { length(), Data }; } - static - String make_length( AllocatorInfo allocator, char const* str, ssize length ); - - static - String fmt( AllocatorInfo allocator, char* buf, ssize buf_size, char const* fmt, ... ); - - static - String fmt_buf( AllocatorInfo allocator, char const* fmt, ... ); - - static - String join( AllocatorInfo allocator, char const** parts, ssize num_parts, char const* glue ) - { - String result = make( allocator, "" ); - - for ( ssize idx = 0; idx < num_parts; ++idx ) - { - result.append( parts[ idx ] ); - - if ( idx < num_parts - 1 ) - result.append( glue ); - } - - return result; - } - - static - bool are_equal( String lhs, String rhs ) - { - if ( lhs.length() != rhs.length() ) - return false; - - for ( ssize idx = 0; idx < lhs.length(); ++idx ) - if ( lhs[ idx ] != rhs[ idx ] ) - return false; - - return true; - } - - static - bool are_equal( String lhs, StrC rhs ) - { - if ( lhs.length() != (rhs.Len) ) - return false; - - for ( ssize idx = 0; idx < lhs.length(); ++idx ) - if ( lhs[idx] != rhs[idx] ) - return false; - - return true; - } - - bool make_space_for( char const* str, ssize add_len ); - - bool append( char c ) - { - return append( & c, 1 ); - } - - bool append( char const* str ) - { - return append( str, str_len( str ) ); - } - - bool append( char const* str, ssize length ) - { - if ( sptr(str) > 0 ) - { - ssize curr_len = this->length(); - - if ( ! make_space_for( str, length ) ) - return false; - - Header& header = get_header(); - - mem_copy( Data + curr_len, str, length ); - - Data[ curr_len + length ] = '\0'; - - header.Length = curr_len + length; - } - return str != nullptr; - } - - bool append( StrC str) - { - return append( str.Ptr, str.Len ); - } - - bool append( const String other ) - { - return append( other.Data, other.length() ); - } - - bool append_fmt( char const* fmt, ... ); - - ssize avail_space() const - { - Header const& - header = * rcast( Header const*, Data - sizeof( Header )); - - return header.Capacity - header.Length; - } - - char& back() - { - return Data[ length() - 1 ]; - } - - bool contains(StrC substring) const - { - Header const& header = * rcast( Header const*, Data - sizeof( Header )); - - if (substring.Len > header.Length) - return false; - - ssize main_len = header.Length; - ssize sub_len = substring.Len; - - for (ssize idx = 0; idx <= main_len - sub_len; ++idx) - { - if (str_compare(Data + idx, substring.Ptr, sub_len) == 0) - return true; - } - - return false; - } - - bool contains(String const& substring) const - { - Header const& header = * rcast( Header const*, Data - sizeof( Header )); - - if (substring.length() > header.Length) - return false; - - ssize main_len = header.Length; - ssize sub_len = substring.length(); - - for (ssize idx = 0; idx <= main_len - sub_len; ++idx) - { - if (str_compare(Data + idx, substring.Data, sub_len) == 0) - return true; - } - - return false; - } - - ssize capacity() const - { - Header const& - header = * rcast( Header const*, Data - sizeof( Header )); - - return header.Capacity; - } - - void clear() - { - get_header().Length = 0; - } - - String duplicate( AllocatorInfo allocator ) const - { - return make_length( allocator, Data, length() ); - } - - void free() - { - if ( ! Data ) - return; - - Header& header = get_header(); - - gen::free( header.Allocator, & header ); - } - - Header& get_header() - { - return *(Header*)(Data - sizeof(Header)); - } - - ssize length() const - { - Header const& - header = * rcast( Header const*, Data - sizeof( Header )); - - return header.Length; - } - - b32 starts_with( StrC substring ) const - { - if (substring.Len > length()) - return false; - - b32 result = str_compare(Data, substring.Ptr, substring.Len ) == 0; - return result; - } - - b32 starts_with( String substring ) const - { - if (substring.length() > length()) - return false; - - b32 result = str_compare(Data, substring, substring.length() - 1 ) == 0; - return result; - } - - void skip_line() - { - #define current (*scanner) - char* scanner = Data; - while ( current != '\r' && current != '\n' ) - { - ++ scanner; - } - - s32 new_length = scanner - Data; - - if ( current == '\r' ) - { - new_length += 1; - } - - mem_move( Data, scanner, new_length ); - - Header* header = & get_header(); - header->Length = new_length; - #undef current - } - - void strip_space() - { - char* write_pos = Data; - char* read_pos = Data; - - while ( * read_pos) - { - if ( ! char_is_space( *read_pos )) - { - *write_pos = *read_pos; - write_pos++; - } - read_pos++; - } - - write_pos[0] = '\0'; // Null-terminate the modified string - - // Update the length if needed - get_header().Length = write_pos - Data; - } - - void trim( char const* cut_set ) - { - ssize len = 0; - - char* start_pos = Data; - char* end_pos = Data + length() - 1; - - while ( start_pos <= end_pos && char_first_occurence( cut_set, *start_pos ) ) - start_pos++; - - while ( end_pos > start_pos && char_first_occurence( cut_set, *end_pos ) ) - end_pos--; - - len = scast( ssize, ( start_pos > end_pos ) ? 0 : ( ( end_pos - start_pos ) + 1 ) ); - - if ( Data != start_pos ) - mem_move( Data, start_pos, len ); - - Data[ len ] = '\0'; - - get_header().Length = len; - } - - void trim_space() - { - return trim( " \t\r\n\v\f" ); - } - - // Debug function that provides a copy of the string with whitespace characters visualized. - String visualize_whitespace() const - { - Header* header = (Header*)(Data - sizeof(Header)); - - String result = make_reserve(header->Allocator, length() * 2); // Assume worst case for space requirements. - - for ( char c : *this ) - { - switch ( c ) - { - case ' ': - result.append( txt("·") ); - break; - case '\t': - result.append( txt("→") ); - break; - case '\n': - result.append( txt("↵") ); - break; - case '\r': - result.append( txt("⏎") ); - break; - case '\v': - result.append( txt("⇕") ); - break; - case '\f': - result.append( txt("⌂") ); - break; - default: - result.append(c); - break; - } - } - - return result; - } - - // For-range support - - char* begin() const - { - return Data; - } - - char* end() const - { - Header const& - header = * rcast( Header const*, Data - sizeof( Header )); - - return Data + header.Length; - } - - operator bool() - { - return Data != nullptr; - } - - operator char* () - { - return Data; - } - - operator char const* () const - { - return Data; - } - - operator StrC() const - { - return { length(), Data }; - } - - // Used with cached strings - // Essentially makes the string a string view. - String const& operator = ( String const& other ) const - { - if ( this == & other ) + String const& operator=(String const& other) const { + if (this == &other) return *this; - String* - this_ = ccast(String*, this); + String* this_ = ccast(String*, this); this_->Data = other.Data; return *this; } - char& operator [] ( ssize index ) - { - return Data[ index ]; - } + forceinline char& operator[](ssize index) { return Data[index]; } + forceinline char const& operator[](ssize index) const { return Data[index]; } - char const& operator [] ( ssize index ) const - { - return Data[ index ]; - } - - char* Data; + forceinline char* begin() const { return Data; } + forceinline char* end() const { return Data + length(); } +#pragma endregion Member Mapping +#endif }; -struct String_POD +inline +usize string_grow_formula(usize value) { + // Using a very aggressive growth formula to reduce time mem_copying with recursive calls to append in this library. + return 4 * value + 8; +} + +inline +String string_make(AllocatorInfo allocator, char const* str) { + ssize length = str ? str_len(str) : 0; + return string_make_length(allocator, str, length); +} + +inline +String string_make(AllocatorInfo allocator, StrC str) { + return string_make_length(allocator, str.Ptr, str.Len); +} + +inline +String string_fmt(AllocatorInfo allocator, char* buf, ssize buf_size, char const* fmt, ...) { + va_list va; + va_start(va, fmt); + str_fmt_va(buf, buf_size, fmt, va); + va_end(va); + + return string_make(allocator, buf); +} + +inline +String string_fmt_buf(AllocatorInfo allocator, char const* fmt, ...) { + local_persist thread_local + char buf[GEN_PRINTF_MAXLEN] = { 0 }; + + va_list va; + va_start(va, fmt); + str_fmt_va(buf, GEN_PRINTF_MAXLEN, fmt, va); + va_end(va); + + return string_make(allocator, buf); +} + +inline +String string_join(AllocatorInfo allocator, char const** parts, ssize num_parts, char const* glue) +{ + String result = string_make(allocator, ""); + + for (ssize idx = 0; idx < num_parts; ++idx) + { + append(result, parts[idx]); + + if (idx < num_parts - 1) + append(result, glue); + } + + return result; +} + +inline +bool append(String& str, char c) { + return append(str, &c, 1); +} + +inline +bool append(String& str, char const* str_to_append) { + return append(str, str_to_append, str_len(str_to_append)); +} + +inline +bool append(String& str, char const* str_to_append, ssize append_length) +{ + if (sptr(str_to_append) > 0) + { + ssize curr_len = length(str); + + if (!make_space_for(str, str_to_append, append_length)) + return false; + + StringHeader& header = get_header(str); + + mem_copy(str.Data + curr_len, str_to_append, append_length); + + str.Data[curr_len + append_length] = '\0'; + + header.Length = curr_len + append_length; + } + return str_to_append != nullptr; +} + +inline +bool append(String& str, StrC str_to_append) { + return append(str, str_to_append.Ptr, str_to_append.Len); +} + +inline +bool append(String& str, const String other) { + return append(str, other.Data, length(other)); +} + +inline +bool are_equal(String lhs, String rhs) +{ + if (length(lhs) != length(rhs)) + return false; + + for (ssize idx = 0; idx < length(lhs); ++idx) + if (lhs[idx] != rhs[idx]) + return false; + + return true; +} + +inline +bool are_equal(String lhs, StrC rhs) +{ + if (length(lhs) != (rhs.Len)) + return false; + + for (ssize idx = 0; idx < length(lhs); ++idx) + if (lhs[idx] != rhs[idx]) + return false; + + return true; +} + +inline +ssize avail_space(String const& str) { + StringHeader const& header = *rcast(StringHeader const*, str.Data - sizeof(StringHeader)); + return header.Capacity - header.Length; +} + +inline +char& back(String& str) { + return str.Data[length(str) - 1]; +} + +inline +bool contains(String const& str, StrC substring) +{ + StringHeader const& header = *rcast(StringHeader const*, str.Data - sizeof(StringHeader)); + + if (substring.Len > header.Length) + return false; + + ssize main_len = header.Length; + ssize sub_len = substring.Len; + + for (ssize idx = 0; idx <= main_len - sub_len; ++idx) + { + if (str_compare(str.Data + idx, substring.Ptr, sub_len) == 0) + return true; + } + + return false; +} + +inline +bool contains(String const& str, String const& substring) +{ + StringHeader const& header = *rcast(StringHeader const*, str.Data - sizeof(StringHeader)); + + if (length(substring) > header.Length) + return false; + + ssize main_len = header.Length; + ssize sub_len = length(substring); + + for (ssize idx = 0; idx <= main_len - sub_len; ++idx) + { + if (str_compare(str.Data + idx, substring.Data, sub_len) == 0) + return true; + } + + return false; +} + +inline +ssize capacity(String const& str) { + StringHeader const& header = *rcast(StringHeader const*, str.Data - sizeof(StringHeader)); + return header.Capacity; +} + +inline +void clear(String& str) { + get_header(str).Length = 0; +} + +inline +String duplicate(String const& str, AllocatorInfo allocator) { + return string_make_length(allocator, str.Data, length(str)); +} + +inline +void free(String& str) { + if (!str.Data) + return; + + StringHeader& header = get_header(str); + GEN_NS free(header.Allocator, &header); +} + +inline +StringHeader& get_header(String& str) { + return *(StringHeader*)(str.Data - sizeof(StringHeader)); +} + +inline +ssize length(String const& str) +{ + StringHeader const& header = *rcast(StringHeader const*, str.Data - sizeof(StringHeader)); + return header.Length; +} + +inline +bool make_space_for(String& str, char const* to_append, ssize add_len) +{ + ssize available = avail_space(str); + + if (available >= add_len) { + return true; + } + else + { + ssize new_len, old_size, new_size; + void* ptr; + void* new_ptr; + + AllocatorInfo allocator = get_header(str).Allocator; + StringHeader* header = nullptr; + + new_len = string_grow_formula(length(str) + add_len); + ptr = &get_header(str); + old_size = size_of(StringHeader) + length(str) + 1; + new_size = size_of(StringHeader) + new_len + 1; + + new_ptr = resize(allocator, ptr, old_size, new_size); + + if (new_ptr == nullptr) + return false; + + header = rcast(StringHeader*, new_ptr); + header->Allocator = allocator; + header->Capacity = new_len; + + str.Data = rcast(char*, header + 1); + + return true; + } +} + +inline +b32 starts_with(String const& str, StrC substring) { + if (substring.Len > length(str)) + return false; + + b32 result = str_compare(str.Data, substring.Ptr, substring.Len) == 0; + return result; +} + +inline +b32 starts_with(String const& str, String substring) { + if (length(substring) > length(str)) + return false; + + b32 result = str_compare(str.Data, substring.Data, length(substring) - 1) == 0; + return result; +} + +inline +void skip_line(String& str) +{ +#define current (*scanner) + char* scanner = str.Data; + while (current != '\r' && current != '\n') { + ++scanner; + } + + s32 new_length = scanner - str.Data; + + if (current == '\r') { + new_length += 1; + } + + mem_move(str.Data, scanner, new_length); + + StringHeader* header = &get_header(str); + header->Length = new_length; +#undef current +} + +inline +void strip_space(String& str) +{ + char* write_pos = str.Data; + char* read_pos = str.Data; + + while (*read_pos) + { + if (!char_is_space(*read_pos)) + { + *write_pos = *read_pos; + write_pos++; + } + read_pos++; + } + + write_pos[0] = '\0'; // Null-terminate the modified string + + // Update the length if needed + get_header(str).Length = write_pos - str.Data; +} + +inline +void trim(String& str, char const* cut_set) +{ + ssize len = 0; + + char* start_pos = str.Data; + char* end_pos = str.Data + length(str) - 1; + + while (start_pos <= end_pos && char_first_occurence(cut_set, *start_pos)) + start_pos++; + + while (end_pos > start_pos && char_first_occurence(cut_set, *end_pos)) + end_pos--; + + len = scast(ssize, (start_pos > end_pos) ? 0 : ((end_pos - start_pos) + 1)); + + if (str.Data != start_pos) + mem_move(str.Data, start_pos, len); + + str.Data[len] = '\0'; + + get_header(str).Length = len; +} + +inline +void trim_space(String& str) { + trim(str, " \t\r\n\v\f"); +} + +inline +String visualize_whitespace(String const& str) +{ + StringHeader* header = (StringHeader*)(str.Data - sizeof(StringHeader)); + String result = string_make_reserve(header->Allocator, length(str) * 2); // Assume worst case for space requirements. + + for (char c : str) switch (c) + { + case ' ': + append(result, txt("·")); + break; + case '\t': + append(result, txt("→")); + break; + case '\n': + append(result, txt("↵")); + break; + case '\r': + append(result, txt("⏎")); + break; + case '\v': + append(result, txt("⇕")); + break; + case '\f': + append(result, txt("⌂")); + break; + default: + append(result, c); + break; + } + + return result; +} +#pragma endregion String + +struct String_POD { char* Data; }; static_assert( sizeof( String_POD ) == sizeof( String ), "String is not a POD" );