diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/FFstrbuf.c | |
Add the files
Diffstat (limited to 'src/common/impl/FFstrbuf.c')
| -rw-r--r-- | src/common/impl/FFstrbuf.c | 903 |
1 files changed, 903 insertions, 0 deletions
diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c new file mode 100644 index 0000000..1f6477b --- /dev/null +++ b/src/common/impl/FFstrbuf.c @@ -0,0 +1,903 @@ +#include "common/FFstrbuf.h" +#include "common/mallocHelper.h" +#include "common/strutil.h" + +#include <ctype.h> +#include <inttypes.h> +#include <math.h> + +char* CHAR_NULL_PTR = ""; + +void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) { + strbuf->allocated = allocate; + + if (strbuf->allocated > 0) { + strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated); + } + + // This will set the length to zero and the null byte. + ffStrbufClear(strbuf); +} + +void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) { + assert(format != NULL); + + char* buffer = NULL; + int len = vasprintf(&buffer, format, arguments); + assert(len >= 0); + + ffStrbufInitMoveNS(strbuf, (uint32_t) len, buffer); +} + +// 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. +void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) { + assert(heapStr != NULL); + + strbuf->length = length; + size_t allocSize = ffMallocUsableSize(heapStr); + if (allocSize == 0) { + allocSize = length + 1; + } else if (allocSize > UINT32_MAX) { + allocSize = UINT32_MAX; + } + strbuf->allocated = (uint32_t) allocSize; + strbuf->chars = heapStr; +} + +void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) { + if (ffStrbufGetFree(strbuf) >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) { + return; + } + + uint32_t allocate = strbuf->allocated; + if (allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) { + allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC; + } + + while ((strbuf->length + free + 1) > allocate) { // + 1 for the null byte + allocate *= 2; + } + + if (strbuf->allocated == 0) { + char* newbuf = malloc(sizeof(*strbuf->chars) * allocate); + if (strbuf->length == 0) { + *newbuf = '\0'; + } else { + memcpy(newbuf, strbuf->chars, strbuf->length + 1); + } + strbuf->chars = newbuf; + } else { + strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate); + } + + strbuf->allocated = allocate; +} + +// Ensure that at least `free` bytes are available in the buffer besides the current length +// for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL) +void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { + uint32_t oldFree = ffStrbufGetFree(strbuf); + if (oldFree >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) { + return; + } + + uint32_t newCap = strbuf->allocated + (free - oldFree); + + if (strbuf->allocated == 0) { + newCap += strbuf->length + 1; + char* newbuf = malloc(sizeof(*strbuf->chars) * newCap); + if (strbuf->length == 0) { + *newbuf = '\0'; + } else { + memcpy(newbuf, strbuf->chars, strbuf->length + 1); + } + strbuf->chars = newbuf; + } else { + strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap); + } + + strbuf->allocated = newCap; +} + +void ffStrbufClear(FFstrbuf* strbuf) { + assert(strbuf != NULL); + + if (strbuf->allocated == 0) { + strbuf->chars = CHAR_NULL_PTR; + } else { + strbuf->chars[0] = '\0'; + } + + strbuf->length = 0; +} + +void ffStrbufAppendC(FFstrbuf* strbuf, char c) { + ffStrbufEnsureFree(strbuf, 1); + strbuf->chars[strbuf->length++] = c; + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { + if (num == 0) { + return; + } + + ffStrbufEnsureFree(strbuf, num); + memset(&strbuf->chars[strbuf->length], c, num); + strbuf->length += num; + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) { + if (value == NULL || length == 0) { + return; + } + + ffStrbufEnsureFree(strbuf, length); + memcpy(&strbuf->chars[strbuf->length], value, length); + strbuf->length += length; + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int)) { + if (value == NULL) { + return; + } + + // Ensure capacity > 0 or the modification below will fail + uint32_t length = (uint32_t) strlen(value); + if (length == 0) { + return; + } + + ffStrbufEnsureFree(strbuf, length); + for (uint32_t i = 0; value[i] != '\0'; i++) { + strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]); + } + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) { + assert(format != NULL); + + va_list copy; + va_copy(copy, arguments); + + uint32_t free = ffStrbufGetFree(strbuf); + int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments); + + if (written > 0 && (uint32_t) written > free) { + ffStrbufEnsureFree(strbuf, (uint32_t) written); + written = vsnprintf(strbuf->chars + strbuf->length, (uint32_t) written + 1, format, copy); + } + + va_end(copy); + + if (written > 0) { + strbuf->length += (uint32_t) written; + } +} + +const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until) { + if (value == NULL) { + return NULL; + } + + const char* end = strchr(value, until); + if (end == NULL) { + ffStrbufAppendS(strbuf, value); + } else { + ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value); + } + return end; +} + +void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) { + assert(format != NULL); + + va_list arguments; + va_start(arguments, format); + + if (strbuf->allocated == 0) { + ffStrbufInitVF(strbuf, format, arguments); + va_end(arguments); + return; + } + + ffStrbufClear(strbuf); + ffStrbufAppendVF(strbuf, format, arguments); + va_end(arguments); +} + +void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) { + assert(format != NULL); + + va_list arguments; + va_start(arguments, format); + ffStrbufAppendVF(strbuf, format, arguments); + va_end(arguments); +} + +void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value) { + if (value == NULL || length == 0) { + return; + } + + 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; +} + +void ffStrbufPrependC(FFstrbuf* strbuf, char c) { + ffStrbufEnsureFree(strbuf, 1); + memmove(strbuf->chars + 1, strbuf->chars, strbuf->length + 1); // + 1 for the null byte + strbuf->chars[0] = c; + strbuf->length += 1; +} + +void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) { + assert(strbuf != NULL); + + if (length == 0) { + ffStrbufClear(strbuf); + return; + } + + assert(value != NULL); + + if (strbuf->allocated <= length) { + char* newBuf = malloc(sizeof(char) * (length + 1)); + memcpy(newBuf, value, length); + if (strbuf->allocated > 0) { + free(strbuf->chars); + } + strbuf->chars = newBuf; + strbuf->allocated = length + 1; + } else { + memmove(strbuf->chars, value, length); + } + + strbuf->length = length; + strbuf->chars[length] = '\0'; +} + +void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) { + assert(value && value != strbuf); + + if (value->length == 0) { + ffStrbufClear(strbuf); + return; + } + + if (value->allocated == 0) // static string + { + if (strbuf->allocated != 0) { + free(strbuf->chars); + strbuf->allocated = 0; + } + strbuf->chars = value->chars; + strbuf->length = value->length; + return; + } + ffStrbufSetNS(strbuf, value->length, value->chars); +} + +void ffStrbufTrimLeft(FFstrbuf* strbuf, char c) { + if (strbuf->length == 0) { + return; + } + + uint32_t index = 0; + while (index < strbuf->length && strbuf->chars[index] == c) { + ++index; + } + + if (index == 0) { + return; + } + + if (strbuf->allocated == 0) { + // static string + strbuf->length -= index; + strbuf->chars += index; + return; + } + + memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index); + strbuf->length -= index; + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufTrimRight(FFstrbuf* strbuf, char c) { + if (strbuf->length == 0) { + return; + } + + if (!ffStrbufEndsWithC(strbuf, c)) { + return; + } + + do { + --strbuf->length; + } while (ffStrbufEndsWithC(strbuf, c)); + + if (strbuf->allocated == 0) { + // static string + ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars); + return; + } + + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufTrimLeftSpace(FFstrbuf* strbuf) { + if (strbuf->length == 0) { + return; + } + + uint32_t index = 0; + while (index < strbuf->length && isspace(strbuf->chars[index])) { + ++index; + } + + if (index == 0) { + return; + } + + if (strbuf->allocated == 0) { + // static string + strbuf->length -= index; + strbuf->chars += index; + return; + } + + memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index); + strbuf->length -= index; + strbuf->chars[strbuf->length] = '\0'; +} + +void ffStrbufTrimRightSpace(FFstrbuf* strbuf) { + if (strbuf->length == 0) { + return; + } + + if (!ffStrbufEndsWithFn(strbuf, isspace)) { + return; + } + + do { + --strbuf->length; + } while (ffStrbufEndsWithFn(strbuf, isspace)); + + if (strbuf->allocated == 0) { + // static string + ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars); + return; + } + + strbuf->chars[strbuf->length] = '\0'; +} + +bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex) { + if (startIndex > strbuf->length || startIndex >= endIndex) { + return false; + } + + if (endIndex > strbuf->length) { + ffStrbufSubstrBefore(strbuf, startIndex); + return true; + } + + ffStrbufEnsureFree(strbuf, 0); + memmove(strbuf->chars + startIndex, strbuf->chars + endIndex, strbuf->length - endIndex); + strbuf->length -= (endIndex - startIndex); + strbuf->chars[strbuf->length] = '\0'; + return true; +} + +void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str) { + uint32_t stringLength = (uint32_t) strlen(str); + + for (uint32_t i = ffStrbufNextIndexS(strbuf, 0, str); i < strbuf->length; i = ffStrbufNextIndexS(strbuf, i, str)) { + ffStrbufRemoveSubstr(strbuf, i, i + stringLength); + } +} + +void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]) { + for (uint32_t i = 0; i < numStrings; i++) { + ffStrbufRemoveS(strbuf, strings[i]); + } +} + +void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace) { + if (strbuf->length == 0) { + return; + } + + ffStrbufEnsureFree(strbuf, 0); + for ( + char* current_pos = memchr(strbuf->chars, find, strbuf->length); + current_pos; + current_pos = memchr( + current_pos + 1, + find, + strbuf->length - (uint32_t) (current_pos + 1 - strbuf->chars))) { + *current_pos = replace; + } +} + +bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) { + if (strbuf->length <= index) { + return false; + } + + if (strbuf->allocated == 0) { + // static string + if (index < strbuf->length) { + ffStrbufInitNS(strbuf, index, strbuf->chars); + } + return true; + } + + strbuf->length = index; + strbuf->chars[strbuf->length] = '\0'; + return true; +} + +bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index) { + if (index >= strbuf->length) { + ffStrbufClear(strbuf); + return true; + } + + if (strbuf->allocated == 0) { + // static string + strbuf->length -= index + 1; + strbuf->chars += index + 1; + return true; + } + + memmove(strbuf->chars, strbuf->chars + index + 1, strbuf->length - index - 1); + strbuf->length -= (index + 1); + strbuf->chars[strbuf->length] = '\0'; + return true; +} + +bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c) { + uint32_t index = ffStrbufFirstIndexC(strbuf, c); + if (index >= strbuf->length) { + return false; + } + ffStrbufSubstrAfter(strbuf, index); + return true; +} + +bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str) { + if (*str == '\0') { + return false; + } + + 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 + if (index >= strbuf->length) { + return false; + } + + ffStrbufSubstrAfter(strbuf, index); + return true; +} + +bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c) { + uint32_t index = ffStrbufLastIndexC(strbuf, c); + if (index >= strbuf->length) { + return false; + } + + ffStrbufSubstrAfter(strbuf, index); + return true; +} + +bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end) { + if (__builtin_expect(start >= end, false)) { + ffStrbufClear(strbuf); + return false; + } + + if (__builtin_expect(start == 0, false)) { + return ffStrbufSubstrBefore(strbuf, end); + } + if (__builtin_expect(end >= strbuf->length, false)) { + return ffStrbufSubstrAfter(strbuf, start - 1); + } + + 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; +} + +uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c) { + uint32_t result = 0; + for (uint32_t i = 0; i < strbuf->length; i++) { + if (strbuf->chars[i] == c) { + result++; + } + } + + return result; +} + +bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end) { + uint32_t endLength = (uint32_t) strlen(end); + if (ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end)) { + ffStrbufSubstrBefore(strbuf, strbuf->length - endLength); + return true; + } + + return false; +} + +bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c) { + if (ffStrbufEndsWithC(strbuf, c)) { + return false; + } + + ffStrbufAppendC(strbuf, c); + return true; +} + +void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) { + fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file); +} + +void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) { + ffStrbufWriteTo(strbuf, file); + fputc('\n', file); +} + +double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) { + char* str_end; + double result = strtod(strbuf->chars, &str_end); + return str_end == strbuf->chars ? defaultValue : result; +} + +uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) { + char* str_end; + unsigned long long result = strtoull(strbuf->chars, &str_end, 10); + return str_end == strbuf->chars ? defaultValue : (uint64_t) result; +} + +int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) { + char* str_end; + long long result = strtoll(strbuf->chars, &str_end, 10); + return str_end == strbuf->chars ? defaultValue : (int64_t) result; +} + +void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value) { + 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); + + assert(end != NULL); + + strbuf->length += (uint32_t) (end - start); +} + +void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value) { + 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); + + assert(end != NULL); + + strbuf->length += (uint32_t) (end - start); +} + +void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros) { + 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; + + if (precision == 0) { + value = round(value); + } + yyjson_val val = {}; + unsafe_yyjson_set_double(&val, value); + if (precision > 0) { + unsafe_yyjson_set_fp_to_fixed(&val, precision); + } + + // Write at most <precision> digits after the decimal point; doesn't append trailing zeros + char* end = yyjson_write_number(&val, start); + + assert(end > start); + + strbuf->length += (uint32_t) (end - start); + + if (__builtin_expect(value > 1e21 || value < -1e21, false)) { + // If the value is too large, yyjson_write_number will write it in scientific notation + return; + } + + if (trailingZeros) { + if (precision > 1) { + for (char* p = end - 1; *p != '.' && p > start; --p) { + --precision; + } + if (precision > 0) { + ffStrbufAppendNC(strbuf, (uint32_t) precision, '0'); + } + } else if (precision == 0 || (precision < 0 && end[-1] == '0')) { + goto removeDecimalPoint; + } + } else { + if (end[-1] == '0') { + 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'; + } + } +} + +void ffStrbufUpperCase(FFstrbuf* strbuf) { + for (uint32_t i = 0; i < strbuf->length; ++i) { + strbuf->chars[i] = (char) toupper(strbuf->chars[i]); + } +} + +void ffStrbufLowerCase(FFstrbuf* strbuf) { + for (uint32_t i = 0; i < strbuf->length; ++i) { + strbuf->chars[i] = (char) tolower(strbuf->chars[i]); + } +} + +void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) { + if (num == 0) { + return; + } + if (index >= strbuf->length) { + index = strbuf->length; + } + + ffStrbufEnsureFree(strbuf, num); + memmove(strbuf->chars + index + num, strbuf->chars + index, strbuf->length - index + 1); + memset(&strbuf->chars[index], c, num); + strbuf->length += num; +} + +bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { + assert(lineptr && n && buffer); + 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; + if (!*lineptr) { + *lineptr = buffer->chars; + } else { + *lineptr += *n; + if (*lineptr >= pBufferEnd) { // non-empty last line + return false; + } + **lineptr = delimiter; + ++*lineptr; + } + if (*lineptr >= pBufferEnd) { // empty last line + return false; + } + + size_t remaining = (size_t) (pBufferEnd - *lineptr); + char* ending = memchr(*lineptr, delimiter, remaining); + if (ending) { + *n = (size_t) (ending - *lineptr); + *ending = '\0'; + } else { + *n = remaining; + } + return true; +} + +void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { + assert(buffer && lineptr && n); + assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); + assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length)); + + if (!*lineptr) { + return; + } + + *lineptr += *n; + if (*lineptr < buffer->chars + buffer->length) { + **lineptr = delimiter; + } +} + +bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) { + if (strbuf->allocated == 0) { + return false; // Doesn't work with static strings + } + + bool changed = false; + for (uint32_t i = 0; i < strbuf->length; i++) { + if (strbuf->chars[i] != ' ') { + continue; + } + + i++; + uint32_t j = i; + for (; j < strbuf->length && strbuf->chars[j] == ' '; j++); + + if (j == i) { + continue; + } + memmove(&strbuf->chars[i], &strbuf->chars[j], strbuf->length - j + 1); + strbuf->length -= j - i; + changed = true; + } + + return changed; +} + +/// @brief Check if a separated string (comp) contains a substring (strbuf). +/// @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. +bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { + if (strbuf->length == 0) { + return true; + } + + if (compLength == 0) { + return false; + } + + for (const char* p = comp; p < comp + compLength;) { + const char* colon = memchr(p, separator, (size_t) (comp + compLength - p)); + if (colon == NULL) { + uint32_t remainingLen = (uint32_t) (comp + compLength - p); + return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0; + } + + uint32_t substrLength = (uint32_t) (colon - p); + if (strbuf->length == substrLength && memcmp(strbuf->chars, p, substrLength) == 0) { + return true; + } + + p = colon + 1; + } + + return false; +} + +/// @brief Case insensitive version of ffStrbufMatchSeparatedNS. +bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { + if (strbuf->length == 0) { + return true; + } + + if (compLength == 0) { + return false; + } + + for (const char* p = comp; p < comp + compLength;) { + const char* colon = memchr(p, separator, (size_t) (comp + compLength - p)); + if (colon == NULL) { + uint32_t remainingLen = (uint32_t) (comp + compLength - p); + return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0; + } + + uint32_t substrLength = (uint32_t) (colon - p); + if (strbuf->length == substrLength && strncasecmp(strbuf->chars, p, substrLength) == 0) { + return true; + } + + p = colon + 1; + } + + return false; +} + +int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) { + if (codepoint <= 0x7F) { + ffStrbufAppendC(strbuf, (char) codepoint); + return 1; + } else if (codepoint <= 0x7FF) { + ffStrbufAppendNS(strbuf, 2, (char[]) { (char) (0xC0 | (codepoint >> 6)), (char) (0x80 | (codepoint & 0x3F)) }); + return 2; + } else if (codepoint <= 0xFFFF) { + ffStrbufAppendNS(strbuf, 3, (char[]) { (char) (0xE0 | (codepoint >> 12)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) }); + return 3; + } else if (codepoint <= 0x10FFFF) { + ffStrbufAppendNS(strbuf, 4, (char[]) { (char) (0xF0 | (codepoint >> 18)), (char) (0x80 | ((codepoint >> 12) & 0x3F)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) }); + return 4; + } + + ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER + return 1; +} + +/// @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. +bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { + uint32_t startIndex = 0; + while (startIndex < strbuf->length) { + uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator); + + uint32_t folderLength = colonIndex - startIndex; + if (folderLength == compLength && memcmp(strbuf->chars + startIndex, comp, compLength) == 0) { + return true; + } + + startIndex = colonIndex + 1; + } + + return false; +} + +bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { + uint32_t startIndex = 0; + while (startIndex < strbuf->length) { + uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator); + + uint32_t folderLength = colonIndex - startIndex; + if (folderLength == compLength && strncasecmp(strbuf->chars + startIndex, comp, compLength) == 0) { + return true; + } + + startIndex = colonIndex + 1; + } + + return false; +} + +bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) { + assert(strbuf); + + if (strbuf->length < 4) { + return false; + } + + // Static string must be converted first. + assert(strbuf->allocated > 0); + + bool changed = false; + uint32_t read = 0; + uint32_t write = 0; + + while (read < strbuf->length) { + if ( + read + 3 < strbuf->length && + strbuf->chars[read] == '\\' && + strbuf->chars[read + 1] == 'x') { + int8_t hi = ffHexCharToInt(strbuf->chars[read + 2]); + int8_t lo = ffHexCharToInt(strbuf->chars[read + 3]); + if (hi >= 0 && lo >= 0) { + 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; +} |