2026-01-06 09:48:38 +08:00
#include "common/FFstrbuf.h"
#include "common/mallocHelper.h"
2026-05-28 15:58:08 +08:00
#include "common/strutil.h"
2026-07-28 14:36:19 +08:00
#include "common/debug.h"
2021-03-19 20:57:07 +01:00
2021-04-09 19:21:56 +02:00
#include <ctype.h>
2022-09-04 22:20:12 +02:00
#include <inttypes.h>
2025-08-20 10:10:30 +08:00
#include <math.h>
2021-03-19 20:57:07 +01:00
2023-03-22 15:32:25 +08:00
char * CHAR_NULL_PTR = "" ;
2021-11-16 12:54:49 +01:00
2026-03-29 09:28:49 +08:00
void ffStrbufInitA ( FFstrbuf * strbuf , uint32_t allocate ) {
2021-03-19 20:57:07 +01:00
strbuf -> allocated = allocate ;
2021-03-27 18:14:19 +01:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated > 0 ) {
2021-11-16 12:54:49 +01:00
strbuf -> chars = ( char * ) malloc ( sizeof ( char ) * strbuf -> allocated );
2026-03-29 09:28:49 +08:00
}
2021-11-16 12:54:49 +01:00
2026-03-29 09:28:49 +08:00
// This will set the length to zero and the null byte.
2021-03-19 20:57:07 +01:00
ffStrbufClear ( strbuf );
}
2026-03-29 09:28:49 +08:00
void ffStrbufInitVF ( FFstrbuf * strbuf , const char * format , va_list arguments ) {
2026-07-10 18:08:04 +08:00
char * buffer = nullptr ;
2025-12-19 23:24:56 +08:00
int len = vasprintf ( & buffer , format , arguments );
2022-12-06 16:26:51 +08:00
assert ( len >= 0 );
2026-03-29 09:28:49 +08:00
ffStrbufInitMoveNS ( strbuf , ( uint32_t ) len , buffer );
2025-12-19 23:24:56 +08:00
}
// Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this
// function; the memory will be managed and freed via the associated FFstrbuf.
2026-03-29 09:28:49 +08:00
void ffStrbufInitMoveNS ( FFstrbuf * strbuf , uint32_t length , char * heapStr ) {
2025-12-19 23:24:56 +08:00
strbuf -> length = length ;
size_t allocSize = ffMallocUsableSize ( heapStr );
2026-03-29 09:28:49 +08:00
if ( allocSize == 0 ) {
2025-12-19 23:24:56 +08:00
allocSize = length + 1 ;
2026-03-29 09:28:49 +08:00
} else if ( allocSize > UINT32_MAX ) {
2025-12-19 23:24:56 +08:00
allocSize = UINT32_MAX ;
2026-03-29 09:28:49 +08:00
}
2025-12-19 23:24:56 +08:00
strbuf -> allocated = ( uint32_t ) allocSize ;
strbuf -> chars = heapStr ;
2022-12-06 16:26:51 +08:00
}
2026-06-30 19:25:51 +08:00
void ffStrbufInitF ( FFstrbuf * strbuf , const char * format , ...) {
va_list arguments ;
va_start ( arguments , format );
ffStrbufInitVF ( strbuf , format , arguments );
va_end ( arguments );
}
FFstrbuf ffStrbufCreateF ( const char * format , ...) {
FFstrbuf strbuf ;
va_list arguments ;
va_start ( arguments , format );
ffStrbufInitVF ( & strbuf , format , arguments );
va_end ( arguments );
return strbuf ;
}
2026-06-29 16:11:29 +08:00
void ffStrbufEnsureFreeNoCheck ( FFstrbuf * strbuf , uint32_t free ) {
2026-07-28 14:36:19 +08:00
uint32_t allocate ;
if ( __builtin_expect ( __builtin_uadd_overflow ( strbuf -> length , free , & allocate ), false )) {
FF_DEBUG ( "Error: Integer overflow when calculating allocation size. Aborting" );
abort ();
}
2026-07-07 19:15:36 +08:00
if ( allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC ) { // `<` for null terminator
2022-09-23 19:54:18 +02:00
allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC ;
2026-07-07 19:15:36 +08:00
} else {
2026-07-28 14:36:19 +08:00
if ( __builtin_expect ( allocate > ( UINT32_MAX >> 1 ), false )) {
// User tried to allocate more than 2GB of memory, which exceeds the maximum size supported by FFstrbuf.
// This is likely an error or an attempt to exploit the program. Abort to prevent potential issues.
FF_DEBUG ( "Error: Attempted to allocate %" PRIu32 " bytes more than 2GB of memory in FFstrbuf. Aborting" , allocate );
abort ();
}
2026-07-07 19:15:36 +08:00
// Round up to the next power of 2.
// If the value is already a power of 2, it will be rounded up to the next power of 2.
2026-08-14 17:54:36 +08:00
// allocate = __builtin_stdc_bit_ceil(allocate + 1); // Currently generates worse asm code
2026-07-07 19:15:36 +08:00
allocate = 1U << ( 32 - __builtin_clz ( allocate ));
2026-03-29 09:28:49 +08:00
}
2021-11-16 12:54:49 +01:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
2023-07-25 16:01:38 +08:00
char * newbuf = malloc ( sizeof ( * strbuf -> chars ) * allocate );
2026-03-29 09:28:49 +08:00
if ( strbuf -> length == 0 ) {
2023-07-25 16:01:38 +08:00
* newbuf = '\0' ;
2026-03-29 09:28:49 +08:00
} else {
2023-07-25 16:01:38 +08:00
memcpy ( newbuf , strbuf -> chars , strbuf -> length + 1 );
2026-03-29 09:28:49 +08:00
}
2023-07-25 16:01:38 +08:00
strbuf -> chars = newbuf ;
2026-03-29 09:28:49 +08:00
} else {
2022-06-08 17:14:11 +02:00
strbuf -> chars = realloc ( strbuf -> chars , sizeof ( * strbuf -> chars ) * allocate );
2026-03-29 09:28:49 +08:00
}
2021-11-17 10:59:31 +01:00
2022-06-08 17:14:11 +02:00
strbuf -> allocated = allocate ;
2021-03-19 20:57:07 +01:00
}
2026-01-09 15:19:46 +08:00
// Ensure that at least `free` bytes are available in the buffer besides the current length
2023-12-22 16:04:56 +09:00
// for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL)
2026-07-28 14:36:19 +08:00
// This function ensures a dynamic buffer is allocated even if free == 0
2026-03-29 09:28:49 +08:00
void ffStrbufEnsureFixedLengthFree ( FFstrbuf * strbuf , uint32_t free ) {
2026-07-28 14:36:19 +08:00
uint32_t newCap ;
2023-12-11 14:00:50 +08:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
2026-07-28 14:36:19 +08:00
assert ( strbuf -> length < UINT32_MAX - 1 ); // We don't use static strings with length >= UINT32_MAX - 1, so this should never happen
if ( __builtin_expect ( __builtin_uadd_overflow ( strbuf -> length + 1 , free , & newCap ), false )) {
FF_DEBUG ( "Error: Integer overflow when calculating new capacity. Aborting" );
abort ();
}
2023-12-03 10:52:39 +09:00
char * newbuf = malloc ( sizeof ( * strbuf -> chars ) * newCap );
2026-03-29 09:28:49 +08:00
if ( strbuf -> length == 0 ) {
2023-12-03 10:52:39 +09:00
* newbuf = '\0' ;
2026-03-29 09:28:49 +08:00
} else {
2023-12-03 10:52:39 +09:00
memcpy ( newbuf , strbuf -> chars , strbuf -> length + 1 );
2026-03-29 09:28:49 +08:00
}
2023-12-03 10:52:39 +09:00
strbuf -> chars = newbuf ;
2026-03-29 09:28:49 +08:00
} else {
2026-07-28 14:36:19 +08:00
uint32_t oldFree = ffStrbufGetFree ( strbuf );
if ( oldFree >= free ) {
return ;
}
if ( __builtin_expect ( __builtin_uadd_overflow ( strbuf -> allocated , free - oldFree , & newCap ), false )) {
FF_DEBUG ( "Error: Integer overflow when calculating new capacity. Aborting" );
abort ();
}
2023-12-03 10:52:39 +09:00
strbuf -> chars = realloc ( strbuf -> chars , sizeof ( * strbuf -> chars ) * newCap );
2026-03-29 09:28:49 +08:00
}
2023-12-11 14:00:50 +08:00
2023-12-03 10:52:39 +09:00
strbuf -> allocated = newCap ;
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendTransformS ( FFstrbuf * strbuf , const char * value , int ( * transformFunc )( int )) {
2026-07-10 18:08:04 +08:00
if ( value == nullptr ) {
2021-03-19 20:57:07 +01:00
return ;
2026-03-29 09:28:49 +08:00
}
2021-03-19 20:57:07 +01:00
2026-03-29 09:28:49 +08:00
// Ensure capacity > 0 or the modification below will fail
2022-09-24 19:22:33 +08:00
uint32_t length = ( uint32_t ) strlen ( value );
2026-03-29 09:28:49 +08:00
if ( length == 0 ) {
2022-09-24 19:22:33 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2022-09-24 19:22:33 +08:00
ffStrbufEnsureFree ( strbuf , length );
2026-03-29 09:28:49 +08:00
for ( uint32_t i = 0 ; value [ i ] != '\0' ; i ++ ) {
2022-01-12 01:02:14 +01:00
strbuf -> chars [ strbuf -> length ++ ] = ( char ) transformFunc ( value [ i ]);
}
strbuf -> chars [ strbuf -> length ] = '\0' ;
2021-03-19 20:57:07 +01:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendVF ( FFstrbuf * strbuf , const char * format , va_list arguments ) {
2022-01-12 18:57:35 +01:00
va_list copy ;
va_copy ( copy , arguments );
2021-11-17 11:41:17 +01:00
uint32_t free = ffStrbufGetFree ( strbuf );
2022-12-02 22:20:01 +08:00
int written = vsnprintf ( strbuf -> chars + strbuf -> length , strbuf -> allocated > 0 ? free + 1 : 0 , format , arguments );
2021-03-19 20:57:07 +01:00
2026-03-29 09:28:49 +08:00
if ( written > 0 && ( uint32_t ) written > free ) {
2026-06-29 16:11:29 +08:00
ffStrbufEnsureFreeNoCheck ( strbuf , ( uint32_t ) written );
2022-12-02 22:20:01 +08:00
written = vsnprintf ( strbuf -> chars + strbuf -> length , ( uint32_t ) written + 1 , format , copy );
2021-03-19 20:57:07 +01:00
}
2021-11-17 11:41:17 +01:00
2022-01-12 18:57:35 +01:00
va_end ( copy );
2026-03-29 09:28:49 +08:00
if ( written > 0 ) {
2022-12-02 22:20:01 +08:00
strbuf -> length += ( uint32_t ) written ;
2026-03-29 09:28:49 +08:00
}
2021-03-19 20:57:07 +01:00
}
2026-03-29 09:28:49 +08:00
const char * ffStrbufAppendSUntilC ( FFstrbuf * strbuf , const char * value , char until ) {
2026-07-10 18:08:04 +08:00
if ( value == nullptr ) {
return nullptr ;
2026-03-29 09:28:49 +08:00
}
2022-10-17 13:19:48 +02:00
2026-02-13 16:05:07 +06:00
const char * end = strchr ( value , until );
2026-07-10 18:08:04 +08:00
if ( end == nullptr ) {
2022-10-17 13:19:48 +02:00
ffStrbufAppendS ( strbuf , value );
2026-03-29 09:28:49 +08:00
} else {
2022-10-17 13:19:48 +02:00
ffStrbufAppendNS ( strbuf , ( uint32_t ) ( end - value ), value );
2026-03-29 09:28:49 +08:00
}
2024-03-24 18:32:50 +08:00
return end ;
2022-10-17 13:19:48 +02:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufSetF ( FFstrbuf * strbuf , const char * format , ...) {
2022-01-12 01:02:14 +01:00
va_list arguments ;
va_start ( arguments , format );
2022-12-06 16:26:51 +08:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
2023-11-08 15:46:44 +03:30
ffStrbufInitVF ( strbuf , format , arguments );
va_end ( arguments );
return ;
}
2022-12-06 16:26:51 +08:00
ffStrbufClear ( strbuf );
2022-01-12 01:02:14 +01:00
ffStrbufAppendVF ( strbuf , format , arguments );
va_end ( arguments );
2021-03-19 20:57:07 +01:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendF ( FFstrbuf * strbuf , const char * format , ...) {
2022-09-24 19:22:33 +08:00
va_list arguments ;
va_start ( arguments , format );
ffStrbufAppendVF ( strbuf , format , arguments );
va_end ( arguments );
2022-09-04 12:43:30 +02:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufPrependNS ( FFstrbuf * strbuf , uint32_t length , const char * value ) {
2026-07-10 18:08:04 +08:00
if ( value == nullptr || length == 0 ) {
2022-09-24 19:22:33 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2022-09-24 19:22:33 +08:00
2022-09-04 12:43:30 +02:00
ffStrbufEnsureFree ( strbuf , length );
memmove ( strbuf -> chars + length , strbuf -> chars , strbuf -> length + 1 ); // + 1 for the null byte
memcpy ( strbuf -> chars , value , length );
strbuf -> length += length ;
}
2026-03-29 09:28:49 +08:00
void ffStrbufPrependC ( FFstrbuf * strbuf , char c ) {
2024-09-26 14:34:51 +08:00
ffStrbufEnsureFree ( strbuf , 1 );
memmove ( strbuf -> chars + 1 , strbuf -> chars , strbuf -> length + 1 ); // + 1 for the null byte
strbuf -> chars [ 0 ] = c ;
strbuf -> length += 1 ;
}
2026-03-29 09:28:49 +08:00
void ffStrbufSetNS ( FFstrbuf * strbuf , uint32_t length , const char * value ) {
if ( length == 0 ) {
2026-01-09 15:19:46 +08:00
ffStrbufClear ( strbuf );
return ;
}
2026-04-09 13:24:10 +08:00
if ( strbuf -> allocated <= length ) {
2026-04-08 18:58:27 +06:00
char * newBuf = malloc ( sizeof ( char ) * ( length + 1 ));
memcpy ( newBuf , value , length );
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated > 0 ) {
2026-01-09 15:19:46 +08:00
free ( strbuf -> chars );
2026-03-29 09:28:49 +08:00
}
2026-04-08 18:58:27 +06:00
strbuf -> chars = newBuf ;
2026-01-09 15:19:46 +08:00
strbuf -> allocated = length + 1 ;
2026-04-08 18:58:27 +06:00
} else {
memmove ( strbuf -> chars , value , length );
2026-01-09 15:19:46 +08:00
}
strbuf -> length = length ;
strbuf -> chars [ length ] = '\0' ;
}
2026-03-29 09:28:49 +08:00
void ffStrbufSet ( FFstrbuf * strbuf , const FFstrbuf * value ) {
2026-09-17 21:00:36 +08:00
// `value` is non-null per the `nonnull(2)` contract; the aliasing check cannot be expressed by an attribute
assert ( value != strbuf );
2026-01-09 15:19:46 +08:00
2026-03-29 09:28:49 +08:00
if ( value -> length == 0 ) {
2026-01-09 15:19:46 +08:00
ffStrbufClear ( strbuf );
return ;
}
if ( value -> allocated == 0 ) // static string
{
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated != 0 ) {
2026-01-09 15:19:46 +08:00
free ( strbuf -> chars );
strbuf -> allocated = 0 ;
}
strbuf -> chars = value -> chars ;
strbuf -> length = value -> length ;
return ;
}
ffStrbufSetNS ( strbuf , value -> length , value -> chars );
}
2026-03-29 09:28:49 +08:00
void ffStrbufTrimLeft ( FFstrbuf * strbuf , char c ) {
if ( strbuf -> length == 0 ) {
2022-09-06 13:24:57 +02:00
return ;
2026-03-29 09:28:49 +08:00
}
2022-09-06 13:24:57 +02:00
2021-03-21 16:52:17 +01:00
uint32_t index = 0 ;
2026-03-29 09:28:49 +08:00
while ( index < strbuf -> length && strbuf -> chars [ index ] == c ) {
2021-03-21 16:52:17 +01:00
++ index ;
2026-03-29 09:28:49 +08:00
}
2021-03-21 16:52:17 +01:00
2026-03-29 09:28:49 +08:00
if ( index == 0 ) {
2021-03-21 16:52:17 +01:00
return ;
2026-03-29 09:28:49 +08:00
}
2021-03-21 16:52:17 +01:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2023-07-25 16:01:38 +08:00
strbuf -> length -= index ;
strbuf -> chars += index ;
return ;
}
2021-04-25 19:12:05 +02:00
memmove ( strbuf -> chars , strbuf -> chars + index , strbuf -> length - index );
2021-03-21 16:52:17 +01:00
strbuf -> length -= index ;
2021-04-25 19:03:59 +02:00
strbuf -> chars [ strbuf -> length ] = '\0' ;
2021-03-21 16:52:17 +01:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufTrimRight ( FFstrbuf * strbuf , char c ) {
if ( strbuf -> length == 0 ) {
2024-01-06 18:33:57 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2024-01-06 18:33:57 +08:00
2026-03-29 09:28:49 +08:00
if ( ! ffStrbufEndsWithC ( strbuf , c )) {
2022-09-06 13:24:57 +02:00
return ;
2026-03-29 09:28:49 +08:00
}
2022-09-06 13:24:57 +02:00
2026-03-29 09:28:49 +08:00
do {
2021-03-21 16:52:17 +01:00
-- strbuf -> length ;
2026-03-29 09:28:49 +08:00
} while ( ffStrbufEndsWithC ( strbuf , c ));
2022-09-06 13:24:57 +02:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2023-07-25 16:01:38 +08:00
ffStrbufInitNS ( strbuf , strbuf -> length , strbuf -> chars );
return ;
}
2021-03-21 16:52:17 +01:00
2023-07-25 16:01:38 +08:00
strbuf -> chars [ strbuf -> length ] = '\0' ;
2021-03-21 16:52:17 +01:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufTrimLeftSpace ( FFstrbuf * strbuf ) {
if ( strbuf -> length == 0 ) {
2025-07-05 08:31:21 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2025-07-05 08:31:21 +08:00
uint32_t index = 0 ;
2026-03-29 09:28:49 +08:00
while ( index < strbuf -> length && isspace ( strbuf -> chars [ index ])) {
2025-07-05 08:31:21 +08:00
++ index ;
2026-03-29 09:28:49 +08:00
}
2025-07-05 08:31:21 +08:00
2026-03-29 09:28:49 +08:00
if ( index == 0 ) {
2025-07-05 08:31:21 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2025-07-05 08:31:21 +08:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2025-07-05 08:31:21 +08:00
strbuf -> length -= index ;
strbuf -> chars += index ;
return ;
}
memmove ( strbuf -> chars , strbuf -> chars + index , strbuf -> length - index );
strbuf -> length -= index ;
strbuf -> chars [ strbuf -> length ] = '\0' ;
}
2026-03-29 09:28:49 +08:00
void ffStrbufTrimRightSpace ( FFstrbuf * strbuf ) {
if ( strbuf -> length == 0 ) {
2024-01-06 18:33:57 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2024-01-06 18:33:57 +08:00
2026-03-29 09:28:49 +08:00
if ( ! ffStrbufEndsWithFn ( strbuf , isspace )) {
2023-12-23 00:06:37 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2023-12-23 00:06:37 +08:00
2026-03-29 09:28:49 +08:00
do {
2023-12-23 00:06:37 +08:00
-- strbuf -> length ;
2026-03-29 09:28:49 +08:00
} while ( ffStrbufEndsWithFn ( strbuf , isspace ));
2023-12-23 00:06:37 +08:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2023-12-23 00:06:37 +08:00
ffStrbufInitNS ( strbuf , strbuf -> length , strbuf -> chars );
return ;
}
strbuf -> chars [ strbuf -> length ] = '\0' ;
}
2026-03-29 09:28:49 +08:00
bool ffStrbufRemoveSubstr ( FFstrbuf * strbuf , uint32_t startIndex , uint32_t endIndex ) {
if ( startIndex > strbuf -> length || startIndex >= endIndex ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2021-03-28 13:02:40 +02:00
2026-03-29 09:28:49 +08:00
if ( endIndex > strbuf -> length ) {
2022-01-12 01:02:14 +01:00
ffStrbufSubstrBefore ( strbuf , startIndex );
2024-07-16 15:39:06 +08:00
return true ;
2021-03-28 13:02:40 +02:00
}
2023-07-25 16:01:38 +08:00
ffStrbufEnsureFree ( strbuf , 0 );
2022-01-12 01:02:14 +01:00
memmove ( strbuf -> chars + startIndex , strbuf -> chars + endIndex , strbuf -> length - endIndex );
strbuf -> length -= ( endIndex - startIndex );
2021-03-28 13:02:40 +02:00
strbuf -> chars [ strbuf -> length ] = '\0' ;
2024-07-16 15:39:06 +08:00
return true ;
2022-01-12 01:02:14 +01:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufRemoveS ( FFstrbuf * strbuf , const char * str ) {
2022-01-12 01:02:14 +01:00
uint32_t stringLength = ( uint32_t ) strlen ( str );
2026-03-29 09:28:49 +08:00
for ( uint32_t i = ffStrbufNextIndexS ( strbuf , 0 , str ); i < strbuf -> length ; i = ffStrbufNextIndexS ( strbuf , i , str )) {
2022-01-12 01:02:14 +01:00
ffStrbufRemoveSubstr ( strbuf , i , i + stringLength );
2026-03-29 09:28:49 +08:00
}
2021-03-28 13:02:40 +02:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufRemoveStrings ( FFstrbuf * strbuf , uint32_t numStrings , const char * strings []) {
for ( uint32_t i = 0 ; i < numStrings ; i ++ ) {
2022-01-12 01:02:14 +01:00
ffStrbufRemoveS ( strbuf , strings [ i ]);
2026-03-29 09:28:49 +08:00
}
2021-03-28 13:02:40 +02:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufReplaceAllC ( FFstrbuf * strbuf , char find , char replace ) {
if ( strbuf -> length == 0 ) {
2023-07-25 16:01:38 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2023-07-25 16:01:38 +08:00
ffStrbufEnsureFree ( strbuf , 0 );
2026-01-09 15:19:46 +08:00
for (
2026-03-29 09:28:49 +08:00
char * current_pos = memchr ( strbuf -> chars , find , strbuf -> length );
2026-01-09 15:19:46 +08:00
current_pos ;
current_pos = memchr (
current_pos + 1 ,
find ,
2026-03-29 09:28:49 +08:00
strbuf -> length - ( uint32_t ) ( current_pos + 1 - strbuf -> chars ))) {
2023-01-02 11:49:41 +08:00
* current_pos = replace ;
2026-03-29 09:28:49 +08:00
}
2023-01-02 11:49:41 +08:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstrBefore ( FFstrbuf * strbuf , uint32_t index ) {
if ( strbuf -> length <= index ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2021-03-28 13:02:40 +02:00
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2026-07-24 02:41:54 +02:00
ffStrbufInitNS ( strbuf , index , strbuf -> chars );
2024-07-16 15:39:06 +08:00
return true ;
2023-07-25 16:01:38 +08:00
}
2021-04-02 16:21:52 +02:00
strbuf -> length = index ;
2021-03-28 13:02:40 +02:00
strbuf -> chars [ strbuf -> length ] = '\0' ;
2024-07-16 15:39:06 +08:00
return true ;
2021-03-28 13:02:40 +02:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstrAfter ( FFstrbuf * strbuf , uint32_t index ) {
if ( index >= strbuf -> length ) {
2021-04-02 16:21:52 +02:00
ffStrbufClear ( strbuf );
2024-07-16 15:39:06 +08:00
return true ;
2021-04-02 16:21:52 +02:00
}
2026-03-29 09:28:49 +08:00
if ( strbuf -> allocated == 0 ) {
// static string
2024-05-22 16:13:36 +08:00
strbuf -> length -= index + 1 ;
strbuf -> chars += index + 1 ;
2024-07-16 15:39:06 +08:00
return true ;
2023-07-25 16:01:38 +08:00
}
2021-04-02 16:21:52 +02:00
memmove ( strbuf -> chars , strbuf -> chars + index + 1 , strbuf -> length - index - 1 );
strbuf -> length -= ( index + 1 );
2021-04-03 23:10:14 +02:00
strbuf -> chars [ strbuf -> length ] = '\0' ;
2024-07-16 15:39:06 +08:00
return true ;
2021-04-02 16:21:52 +02:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstrAfterFirstC ( FFstrbuf * strbuf , char c ) {
2021-05-07 17:10:22 +02:00
uint32_t index = ffStrbufFirstIndexC ( strbuf , c );
2026-03-29 09:28:49 +08:00
if ( index >= strbuf -> length ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2024-07-16 15:39:06 +08:00
ffStrbufSubstrAfter ( strbuf , index );
return true ;
2021-05-07 17:10:22 +02:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstrAfterFirstS ( FFstrbuf * strbuf , const char * str ) {
if ( * str == '\0' ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2022-01-12 01:02:14 +01:00
uint32_t index = ffStrbufFirstIndexS ( strbuf , str ) + ( uint32_t ) strlen ( str ) - 1 ; // -1, because firstIndexS is already pointing to str[0], we want to add only the remaining length
2026-03-29 09:28:49 +08:00
if ( index >= strbuf -> length ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2024-07-16 15:39:06 +08:00
ffStrbufSubstrAfter ( strbuf , index );
return true ;
2022-01-10 20:20:18 +01:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstrAfterLastC ( FFstrbuf * strbuf , char c ) {
2021-04-02 16:21:52 +02:00
uint32_t index = ffStrbufLastIndexC ( strbuf , c );
2026-03-29 09:28:49 +08:00
if ( index >= strbuf -> length ) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2024-07-16 15:39:06 +08:00
ffStrbufSubstrAfter ( strbuf , index );
return true ;
2021-04-02 16:21:52 +02:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSubstr ( FFstrbuf * strbuf , uint32_t start , uint32_t end ) {
if ( __builtin_expect ( start >= end , false )) {
2025-07-28 15:32:18 +08:00
ffStrbufClear ( strbuf );
return false ;
}
2026-03-29 09:28:49 +08:00
if ( __builtin_expect ( start == 0 , false )) {
return ffStrbufSubstrBefore ( strbuf , end );
}
if ( __builtin_expect ( end >= strbuf -> length , false )) {
return ffStrbufSubstrAfter ( strbuf , start - 1 );
}
2025-07-28 15:32:18 +08:00
uint32_t len = end - start ;
ffStrbufEnsureFixedLengthFree ( strbuf , len ); // In case of static string
memmove ( strbuf -> chars , strbuf -> chars + start , len );
strbuf -> length = len ;
strbuf -> chars [ len ] = '\0' ;
return true ;
}
2026-03-29 09:28:49 +08:00
uint32_t ffStrbufCountC ( const FFstrbuf * strbuf , char c ) {
2022-04-21 17:59:26 +02:00
uint32_t result = 0 ;
2026-03-29 09:28:49 +08:00
for ( uint32_t i = 0 ; i < strbuf -> length ; i ++ ) {
if ( strbuf -> chars [ i ] == c ) {
2022-04-21 17:59:26 +02:00
result ++ ;
2026-03-29 09:28:49 +08:00
}
2022-04-21 17:59:26 +02:00
}
2022-09-24 19:22:33 +08:00
2022-04-21 17:59:26 +02:00
return result ;
}
2026-03-29 09:28:49 +08:00
bool ffStrbufRemoveIgnCaseEndS ( FFstrbuf * strbuf , const char * end ) {
2022-09-24 23:17:38 +08:00
uint32_t endLength = ( uint32_t ) strlen ( end );
2026-03-29 09:28:49 +08:00
if ( ffStrbufEndsWithIgnCaseNS ( strbuf , endLength , end )) {
2021-06-26 16:48:15 +02:00
ffStrbufSubstrBefore ( strbuf , strbuf -> length - endLength );
return true ;
}
return false ;
}
2026-03-29 09:28:49 +08:00
bool ffStrbufEnsureEndsWithC ( FFstrbuf * strbuf , char c ) {
if ( ffStrbufEndsWithC ( strbuf , c )) {
2024-07-16 15:39:06 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2024-07-16 15:39:06 +08:00
ffStrbufAppendC ( strbuf , c );
return true ;
2022-06-22 20:14:10 +02:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendSInt ( FFstrbuf * strbuf , int64_t value ) {
2025-08-19 15:00:30 +08:00
ffStrbufEnsureFree ( strbuf , 21 ); // Required by yyjson_write_number
char * start = strbuf -> chars + strbuf -> length ;
yyjson_val val = {};
unsafe_yyjson_set_sint ( & val , value );
char * end = yyjson_write_number ( & val , start );
2026-07-10 18:08:04 +08:00
assert ( end != nullptr );
2025-08-19 15:00:30 +08:00
2026-03-29 09:28:49 +08:00
strbuf -> length += ( uint32_t ) ( end - start );
2025-08-19 15:00:30 +08:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendUInt ( FFstrbuf * strbuf , uint64_t value ) {
2025-08-19 15:00:30 +08:00
ffStrbufEnsureFree ( strbuf , 21 ); // Required by yyjson_write_number
char * start = strbuf -> chars + strbuf -> length ;
yyjson_val val = {};
unsafe_yyjson_set_uint ( & val , value );
char * end = yyjson_write_number ( & val , start );
2026-07-10 18:08:04 +08:00
assert ( end != nullptr );
2025-08-19 15:00:30 +08:00
2026-03-29 09:28:49 +08:00
strbuf -> length += ( uint32_t ) ( end - start );
2025-08-19 15:00:30 +08:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufAppendDouble ( FFstrbuf * strbuf , double value , int8_t precision , bool trailingZeros ) {
2025-08-19 15:00:30 +08:00
assert ( precision <= 15 ); // yyjson_write_number supports up to 15 digits after the decimal point
ffStrbufEnsureFree ( strbuf , 40 ); // Required by yyjson_write_number
char * start = strbuf -> chars + strbuf -> length ;
2026-03-29 09:28:49 +08:00
if ( precision == 0 ) {
2025-08-20 10:10:30 +08:00
value = round ( value );
2026-03-29 09:28:49 +08:00
}
2025-08-19 15:00:30 +08:00
yyjson_val val = {};
unsafe_yyjson_set_double ( & val , value );
2026-03-29 09:28:49 +08:00
if ( precision > 0 ) {
2025-08-20 10:10:30 +08:00
unsafe_yyjson_set_fp_to_fixed ( & val , precision );
2026-03-29 09:28:49 +08:00
}
2025-08-19 15:00:30 +08:00
// Write at most <precision> digits after the decimal point; doesn't append trailing zeros
char * end = yyjson_write_number ( & val , start );
2025-08-19 16:03:04 +08:00
assert ( end > start );
2025-08-19 15:00:30 +08:00
2026-03-29 09:28:49 +08:00
strbuf -> length += ( uint32_t ) ( end - start );
2025-08-19 15:00:30 +08:00
2026-03-29 09:28:49 +08:00
if ( __builtin_expect ( value > 1e21 || value < - 1e21 , false )) {
2025-08-19 15:00:30 +08:00
// If the value is too large, yyjson_write_number will write it in scientific notation
return ;
}
2026-03-29 09:28:49 +08:00
if ( trailingZeros ) {
if ( precision > 1 ) {
for ( char * p = end - 1 ; * p != '.' && p > start ; -- p ) {
2025-09-02 14:12:58 +08:00
-- precision ;
2026-03-29 09:28:49 +08:00
}
if ( precision > 0 ) {
2025-09-02 14:12:58 +08:00
ffStrbufAppendNC ( strbuf , ( uint32_t ) precision , '0' );
2026-03-29 09:28:49 +08:00
}
} else if ( precision == 0 || ( precision < 0 && end [ - 1 ] == '0' )) {
2025-09-02 14:12:58 +08:00
goto removeDecimalPoint ;
}
2026-03-29 09:28:49 +08:00
} else {
if ( end [ - 1 ] == '0' ) {
2025-09-02 14:12:58 +08:00
removeDecimalPoint :
// yyjson always appends ".0" to make it a float point number. We need to remove it
strbuf -> length -= 2 ;
strbuf -> chars [ strbuf -> length ] = '\0' ;
}
2025-08-19 15:00:30 +08:00
}
}
2026-03-29 09:28:49 +08:00
void ffStrbufUpperCase ( FFstrbuf * strbuf ) {
for ( uint32_t i = 0 ; i < strbuf -> length ; ++ i ) {
2023-07-29 21:18:26 +08:00
strbuf -> chars [ i ] = ( char ) toupper ( strbuf -> chars [ i ]);
2026-03-29 09:28:49 +08:00
}
2023-07-29 21:18:26 +08:00
}
2026-03-29 09:28:49 +08:00
void ffStrbufLowerCase ( FFstrbuf * strbuf ) {
for ( uint32_t i = 0 ; i < strbuf -> length ; ++ i ) {
2023-07-29 21:18:26 +08:00
strbuf -> chars [ i ] = ( char ) tolower ( strbuf -> chars [ i ]);
2026-03-29 09:28:49 +08:00
}
2023-07-29 21:18:26 +08:00
}
2024-09-26 14:34:51 +08:00
2026-03-29 09:28:49 +08:00
void ffStrbufInsertNC ( FFstrbuf * strbuf , uint32_t index , uint32_t num , char c ) {
if ( num == 0 ) {
return ;
}
if ( index >= strbuf -> length ) {
2024-09-26 14:34:51 +08:00
index = strbuf -> length ;
2026-03-29 09:28:49 +08:00
}
2024-09-26 14:34:51 +08:00
ffStrbufEnsureFree ( strbuf , num );
memmove ( strbuf -> chars + index + num , strbuf -> chars + index , strbuf -> length - index + 1 );
memset ( & strbuf -> chars [ index ], c , num );
strbuf -> length += num ;
}
2024-11-26 14:47:02 +08:00
2026-03-29 09:28:49 +08:00
bool ffStrbufGetdelim ( char ** lineptr , size_t * n , char delimiter , FFstrbuf * buffer ) {
2024-11-26 14:47:02 +08:00
assert ( buffer -> allocated > 0 || ( buffer -> allocated == 0 && buffer -> length == 0 ));
assert ( !* lineptr || ( * lineptr >= buffer -> chars && * lineptr <= buffer -> chars + buffer -> length ));
const char * pBufferEnd = buffer -> chars + buffer -> length ;
2026-03-29 09:28:49 +08:00
if ( !* lineptr ) {
2024-11-26 14:47:02 +08:00
* lineptr = buffer -> chars ;
2026-03-29 09:28:49 +08:00
} else {
2024-11-26 14:47:02 +08:00
* lineptr += * n ;
2026-03-29 09:28:49 +08:00
if ( * lineptr >= pBufferEnd ) { // non-empty last line
2024-11-26 14:47:02 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2025-11-25 15:02:02 +08:00
** lineptr = delimiter ;
2024-11-26 14:47:02 +08:00
++* lineptr ;
}
2026-03-29 09:28:49 +08:00
if ( * lineptr >= pBufferEnd ) { // empty last line
2024-11-26 14:47:02 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2024-11-26 14:47:02 +08:00
size_t remaining = ( size_t ) ( pBufferEnd - * lineptr );
2025-11-25 15:02:02 +08:00
char * ending = memchr ( * lineptr , delimiter , remaining );
2026-03-29 09:28:49 +08:00
if ( ending ) {
2024-11-26 14:47:02 +08:00
* n = ( size_t ) ( ending - * lineptr );
* ending = '\0' ;
2026-03-29 09:28:49 +08:00
} else {
2024-11-26 14:47:02 +08:00
* n = remaining ;
2026-03-29 09:28:49 +08:00
}
2024-11-26 14:47:02 +08:00
return true ;
}
2024-11-26 16:35:33 +08:00
2026-03-29 09:28:49 +08:00
void ffStrbufGetdelimRestore ( char ** lineptr , size_t * n , char delimiter , FFstrbuf * buffer ) {
2025-01-06 10:57:29 +08:00
assert ( buffer -> allocated > 0 || ( buffer -> allocated == 0 && buffer -> length == 0 ));
assert ( !* lineptr || ( * lineptr >= buffer -> chars && * lineptr <= buffer -> chars + buffer -> length ));
2026-03-29 09:28:49 +08:00
if ( !* lineptr ) {
2025-01-06 10:57:29 +08:00
return ;
2026-03-29 09:28:49 +08:00
}
2025-01-06 10:57:29 +08:00
* lineptr += * n ;
2026-03-29 09:28:49 +08:00
if ( * lineptr < buffer -> chars + buffer -> length ) {
2025-11-25 15:02:02 +08:00
** lineptr = delimiter ;
2026-03-29 09:28:49 +08:00
}
2025-01-06 10:57:29 +08:00
}
2026-03-29 09:28:49 +08:00
bool ffStrbufRemoveDupWhitespaces ( FFstrbuf * strbuf ) {
if ( strbuf -> allocated == 0 ) {
return false ; // Doesn't work with static strings
}
2024-11-26 16:35:33 +08:00
bool changed = false ;
2026-03-29 09:28:49 +08:00
for ( uint32_t i = 0 ; i < strbuf -> length ; i ++ ) {
if ( strbuf -> chars [ i ] != ' ' ) {
continue ;
}
2024-11-26 16:35:33 +08:00
i ++ ;
uint32_t j = i ;
for (; j < strbuf -> length && strbuf -> chars [ j ] == ' ' ; j ++ );
2026-03-29 09:28:49 +08:00
if ( j == i ) {
continue ;
}
2024-11-26 16:35:33 +08:00
memmove ( & strbuf -> chars [ i ], & strbuf -> chars [ j ], strbuf -> length - j + 1 );
strbuf -> length -= j - i ;
changed = true ;
}
return changed ;
}
2025-05-21 16:40:42 +08:00
2025-09-19 14:13:01 +08:00
/// @brief Check if a separated string (comp) contains a substring (strbuf).
2025-05-21 16:40:42 +08:00
/// @param strbuf The substring to check.
/// @param compLength The length of the separated string to check.
/// @param comp The separated string to check.
/// @param separator The separator character.
2026-09-15 10:33:00 +08:00
///
/// Empty-string handling:
/// - If `strbuf` is empty, it matches only if `comp` contains an empty segment
/// (e.g. "abc::def", "abc:", ":abc") or `comp` itself is empty.
/// - If `comp` is empty (compLength == 0), it matches only if `strbuf` is also empty.
2026-03-29 09:28:49 +08:00
bool ffStrbufMatchSeparatedNS ( const FFstrbuf * strbuf , uint32_t compLength , const char * comp , char separator ) {
2026-09-15 10:33:00 +08:00
if ( __builtin_expect ( compLength == 0 , false )) {
return strbuf -> length == 0 ;
2026-03-29 09:28:49 +08:00
}
2025-05-21 16:40:42 +08:00
2026-09-15 10:33:00 +08:00
for ( const char * p = comp ; p <= comp + compLength ;) {
2026-04-28 19:45:19 +08:00
const char * colon = memchr ( p , separator , ( size_t ) ( comp + compLength - p ));
2026-07-10 18:08:04 +08:00
if ( colon == nullptr ) {
2026-04-28 19:45:19 +08:00
uint32_t remainingLen = ( uint32_t ) ( comp + compLength - p );
2026-04-27 22:49:31 +08:00
return strbuf -> length == remainingLen && memcmp ( strbuf -> chars , p , remainingLen ) == 0 ;
2026-03-29 09:28:49 +08:00
}
2025-05-21 16:40:42 +08:00
uint32_t substrLength = ( uint32_t ) ( colon - p );
2026-03-29 09:28:49 +08:00
if ( strbuf -> length == substrLength && memcmp ( strbuf -> chars , p , substrLength ) == 0 ) {
2025-05-21 16:40:42 +08:00
return true ;
2026-03-29 09:28:49 +08:00
}
2025-05-21 16:40:42 +08:00
p = colon + 1 ;
}
return false ;
}
2025-07-29 16:36:21 +08:00
2025-09-19 14:13:01 +08:00
/// @brief Case insensitive version of ffStrbufMatchSeparatedNS.
2026-03-29 09:28:49 +08:00
bool ffStrbufMatchSeparatedIgnCaseNS ( const FFstrbuf * strbuf , uint32_t compLength , const char * comp , char separator ) {
2026-09-15 10:33:00 +08:00
if ( __builtin_expect ( compLength == 0 , false )) {
return strbuf -> length == 0 ;
2026-03-29 09:28:49 +08:00
}
2025-09-19 14:13:01 +08:00
2026-09-15 10:33:00 +08:00
for ( const char * p = comp ; p <= comp + compLength ;) {
2026-04-28 19:45:19 +08:00
const char * colon = memchr ( p , separator , ( size_t ) ( comp + compLength - p ));
2026-07-10 18:08:04 +08:00
if ( colon == nullptr ) {
2026-04-28 19:45:19 +08:00
uint32_t remainingLen = ( uint32_t ) ( comp + compLength - p );
2026-04-27 22:49:31 +08:00
return strbuf -> length == remainingLen && strncasecmp ( strbuf -> chars , p , remainingLen ) == 0 ;
2026-03-29 09:28:49 +08:00
}
2025-09-19 14:13:01 +08:00
uint32_t substrLength = ( uint32_t ) ( colon - p );
2026-03-29 09:28:49 +08:00
if ( strbuf -> length == substrLength && strncasecmp ( strbuf -> chars , p , substrLength ) == 0 ) {
2025-09-19 14:13:01 +08:00
return true ;
2026-03-29 09:28:49 +08:00
}
2025-09-19 14:13:01 +08:00
p = colon + 1 ;
}
return false ;
}
2026-03-29 09:28:49 +08:00
int ffStrbufAppendUtf32CodePoint ( FFstrbuf * strbuf , uint32_t codepoint ) {
2025-07-29 16:36:21 +08:00
if ( codepoint <= 0x7F ) {
2026-03-29 09:28:49 +08:00
ffStrbufAppendC ( strbuf , ( char ) codepoint );
2025-07-29 16:36:21 +08:00
return 1 ;
} else if ( codepoint <= 0x7FF ) {
2026-03-30 10:27:23 +08:00
ffStrbufAppendNS ( strbuf , 2 , ( char []) { ( char ) ( 0xC0 | ( codepoint >> 6 )), ( char ) ( 0x80 | ( codepoint & 0x3F )) });
2025-07-29 16:36:21 +08:00
return 2 ;
} else if ( codepoint <= 0xFFFF ) {
2026-03-30 10:27:23 +08:00
ffStrbufAppendNS ( strbuf , 3 , ( char []) { ( char ) ( 0xE0 | ( codepoint >> 12 )), ( char ) ( 0x80 | (( codepoint >> 6 ) & 0x3F )), ( char ) ( 0x80 | ( codepoint & 0x3F )) });
2025-07-29 16:36:21 +08:00
return 3 ;
} else if ( codepoint <= 0x10FFFF ) {
2026-03-30 10:27:23 +08:00
ffStrbufAppendNS ( strbuf , 4 , ( char []) { ( char ) ( 0xF0 | ( codepoint >> 18 )), ( char ) ( 0x80 | (( codepoint >> 12 ) & 0x3F )), ( char ) ( 0x80 | (( codepoint >> 6 ) & 0x3F )), ( char ) ( 0x80 | ( codepoint & 0x3F )) });
2025-07-29 16:36:21 +08:00
return 4 ;
}
ffStrbufAppendS ( strbuf , "� " ); // U+FFFD REPLACEMENT CHARACTER
return 1 ;
}
2025-09-19 14:13:01 +08:00
/// @brief Check if a separated string (strbuf) contains a substring (comp).
/// @param strbuf The separated to check.
/// @param compLength The length of the separated string to check.
/// @param comp The substring to check.
/// @param separator The separator character.
2026-09-15 10:33:00 +08:00
///
/// Empty-string handling is symmetric to ffStrbufMatchSeparatedNS
2026-03-29 09:28:49 +08:00
bool ffStrbufSeparatedContainNS ( const FFstrbuf * strbuf , uint32_t compLength , const char * comp , char separator ) {
2026-09-15 10:33:00 +08:00
if ( __builtin_expect ( strbuf -> length == 0 , false )) {
return compLength == 0 ; // An empty list contains only the empty segment
}
2025-09-19 14:13:01 +08:00
uint32_t startIndex = 0 ;
2026-09-15 10:33:00 +08:00
while ( startIndex <= strbuf -> length ) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
2025-09-19 14:13:01 +08:00
uint32_t colonIndex = ffStrbufNextIndexC ( strbuf , startIndex , separator );
uint32_t folderLength = colonIndex - startIndex ;
2026-03-29 09:28:49 +08:00
if ( folderLength == compLength && memcmp ( strbuf -> chars + startIndex , comp , compLength ) == 0 ) {
2025-09-19 14:13:01 +08:00
return true ;
2026-03-29 09:28:49 +08:00
}
2025-09-19 14:13:01 +08:00
startIndex = colonIndex + 1 ;
}
return false ;
}
2026-03-29 09:28:49 +08:00
bool ffStrbufSeparatedContainIgnCaseNS ( const FFstrbuf * strbuf , uint32_t compLength , const char * comp , char separator ) {
2026-09-15 10:33:00 +08:00
if ( __builtin_expect ( strbuf -> length == 0 , false )) {
return compLength == 0 ; // An empty list contains only the empty segment
}
2025-09-19 14:13:01 +08:00
uint32_t startIndex = 0 ;
2026-09-15 10:33:00 +08:00
while ( startIndex <= strbuf -> length ) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
2025-09-19 14:13:01 +08:00
uint32_t colonIndex = ffStrbufNextIndexC ( strbuf , startIndex , separator );
uint32_t folderLength = colonIndex - startIndex ;
2026-03-29 09:28:49 +08:00
if ( folderLength == compLength && strncasecmp ( strbuf -> chars + startIndex , comp , compLength ) == 0 ) {
2025-09-19 14:13:01 +08:00
return true ;
2026-03-29 09:28:49 +08:00
}
2025-09-19 14:13:01 +08:00
startIndex = colonIndex + 1 ;
}
return false ;
}
2026-03-25 14:13:03 +08:00
2026-03-29 09:28:49 +08:00
bool ffStrbufDecodeHexEscapeSequences ( FFstrbuf * strbuf ) {
if ( strbuf -> length < 4 ) {
2026-03-25 14:13:03 +08:00
return false ;
2026-03-29 09:28:49 +08:00
}
2026-03-25 14:13:03 +08:00
// Static string must be converted first.
assert ( strbuf -> allocated > 0 );
bool changed = false ;
uint32_t read = 0 ;
uint32_t write = 0 ;
2026-03-29 09:28:49 +08:00
while ( read < strbuf -> length ) {
2026-03-25 14:13:03 +08:00
if (
read + 3 < strbuf -> length &&
strbuf -> chars [ read ] == '\\' &&
2026-03-29 09:28:49 +08:00
strbuf -> chars [ read + 1 ] == 'x' ) {
2026-03-25 14:13:03 +08:00
int8_t hi = ffHexCharToInt ( strbuf -> chars [ read + 2 ]);
int8_t lo = ffHexCharToInt ( strbuf -> chars [ read + 3 ]);
2026-03-29 09:28:49 +08:00
if ( hi >= 0 && lo >= 0 ) {
2026-03-25 14:13:03 +08:00
strbuf -> chars [ write ++ ] = ( char ) (( hi << 4 ) | lo );
read += 4 ;
changed = true ;
continue ;
}
}
strbuf -> chars [ write ++ ] = strbuf -> chars [ read ++ ];
}
strbuf -> length = write ;
strbuf -> chars [ write ] = '\0' ;
return changed ;
}