Files
fastfetch/src/util/FFstrbuf.c
T

857 lines
23 KiB
C
Raw Normal View History

2021-03-19 20:57:07 +01:00
#include "FFstrbuf.h"
2021-04-09 19:21:56 +02:00
#include <ctype.h>
2022-09-04 22:20:12 +02:00
#include <inttypes.h>
#include <math.h>
2021-03-19 20:57:07 +01:00
char* CHAR_NULL_PTR = "";
2021-11-16 12:54:49 +01:00
2021-03-19 20:57:07 +01:00
void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate)
{
strbuf->allocated = allocate;
2021-03-27 18:14:19 +01:00
2021-11-16 12:54:49 +01:00
if(strbuf->allocated > 0)
strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated);
//This will set the length to zero and the null byte.
2021-03-19 20:57:07 +01:00
ffStrbufClear(strbuf);
}
2022-12-06 16:26:51 +08:00
void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments)
{
assert(format != NULL);
int len = vasprintf(&strbuf->chars, format, arguments);
assert(len >= 0);
strbuf->allocated = (uint32_t)(len + 1);
strbuf->length = (uint32_t)len;
}
2021-03-19 20:57:07 +01:00
void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free)
{
2023-07-25 16:01:38 +08:00
if(ffStrbufGetFree(strbuf) >= free && !(strbuf->allocated == 0 && strbuf->length > 0))
2021-11-16 12:54:49 +01:00
return;
2021-03-19 20:57:07 +01:00
uint32_t allocate = strbuf->allocated;
if(allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC)
2022-09-23 19:54:18 +02:00
allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC;
2021-11-16 12:54:49 +01:00
2021-11-17 10:59:31 +01:00
while((strbuf->length + free + 1) > allocate) // + 1 for the null byte
2021-03-19 20:57:07 +01:00
allocate *= 2;
2021-11-16 12:54:49 +01:00
2021-11-17 10:59:31 +01:00
if(strbuf->allocated == 0)
2022-06-08 17:14:11 +02:00
{
2023-07-25 16:01:38 +08:00
char* newbuf = malloc(sizeof(*strbuf->chars) * allocate);
if(strbuf->length == 0)
*newbuf = '\0';
else
memcpy(newbuf, strbuf->chars, strbuf->length + 1);
strbuf->chars = newbuf;
2022-06-08 17:14:11 +02:00
}
else
strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate);
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
}
2023-12-22 16:04:56 +09:00
// 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)
{
2023-12-22 16:04:56 +09:00
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;
}
2021-03-19 20:57:07 +01:00
void ffStrbufClear(FFstrbuf* strbuf)
{
2022-09-24 19:22:33 +08:00
assert(strbuf != NULL);
2021-11-16 12:54:49 +01:00
if(strbuf->allocated == 0)
strbuf->chars = CHAR_NULL_PTR;
else
strbuf->chars[0] = '\0';
2021-03-19 20:57:07 +01:00
strbuf->length = 0;
}
2022-01-12 01:02:14 +01:00
void ffStrbufAppendC(FFstrbuf* strbuf, char c)
2021-04-18 15:19:21 +02:00
{
2022-01-12 01:02:14 +01:00
ffStrbufEnsureFree(strbuf, 1);
strbuf->chars[strbuf->length++] = c;
2021-04-18 15:19:21 +02:00
strbuf->chars[strbuf->length] = '\0';
}
2023-09-15 10:35:12 +08:00
void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c)
{
if (num == 0) return;
2023-09-15 10:35:12 +08:00
ffStrbufEnsureFree(strbuf, num);
memset(&strbuf->chars[strbuf->length], c, num);
strbuf->length += num;
strbuf->chars[strbuf->length] = '\0';
}
2021-03-19 20:57:07 +01:00
void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value)
{
2022-09-24 19:22:33 +08:00
if(value == NULL || length == 0)
2021-03-19 20:57:07 +01:00
return;
ffStrbufEnsureFree(strbuf, length);
memcpy(&strbuf->chars[strbuf->length], value, length);
strbuf->length += length;
2021-05-23 19:40:34 +02:00
strbuf->chars[strbuf->length] = '\0';
}
2022-01-12 01:02:14 +01:00
void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int(*transformFunc)(int))
2021-03-19 20:57:07 +01:00
{
2022-01-12 01:02:14 +01:00
if(value == NULL)
2021-03-19 20:57:07 +01:00
return;
2022-09-24 19:22:33 +08:00
//Ensure capacity > 0 or the modification below will fail
uint32_t length = (uint32_t) strlen(value);
if(length == 0)
return;
ffStrbufEnsureFree(strbuf, length);
2022-01-12 01:02:14 +01:00
for(uint32_t i = 0; value[i] != '\0'; i++)
{
strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]);
}
strbuf->chars[strbuf->length] = '\0';
2021-03-19 20:57:07 +01:00
}
void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments)
{
2022-09-24 19:22:33 +08:00
assert(format != NULL);
2021-03-19 20:57:07 +01:00
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);
int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments);
2021-03-19 20:57:07 +01:00
if(written > 0 && strbuf->length + (uint32_t) written > free)
2021-03-19 20:57:07 +01:00
{
ffStrbufEnsureFree(strbuf, (uint32_t) written);
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);
if(written > 0)
strbuf->length += (uint32_t) written;
2021-03-19 20:57:07 +01:00
}
2024-03-24 18:32:50 +08:00
const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until)
2022-10-17 13:19:48 +02:00
{
if(value == NULL)
2024-03-24 18:32:50 +08:00
return NULL;
2022-10-17 13:19:48 +02:00
char* end = strchr(value, until);
if(end == NULL)
ffStrbufAppendS(strbuf, value);
else
ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value);
2024-03-24 18:32:50 +08:00
return end;
2022-10-17 13:19:48 +02:00
}
2022-09-24 19:22:33 +08:00
void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...)
2021-03-19 20:57:07 +01:00
{
2022-09-24 19:22:33 +08:00
assert(format != NULL);
2022-01-12 01:02:14 +01:00
va_list arguments;
va_start(arguments, format);
2022-12-06 16:26:51 +08:00
2023-11-08 15:46:44 +03:30
if(strbuf->allocated == 0) {
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
}
2022-09-24 19:22:33 +08:00
void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...)
2022-09-04 12:43:30 +02:00
{
2022-09-24 19:22:33 +08:00
assert(format != NULL);
va_list arguments;
va_start(arguments, format);
ffStrbufAppendVF(strbuf, format, arguments);
va_end(arguments);
2022-09-04 12:43:30 +02:00
}
void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value)
{
2022-09-24 19:22:33 +08:00
if(value == NULL || length == 0)
return;
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;
}
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 ffStrbufTrimLeft(FFstrbuf* strbuf, char c)
{
2023-07-25 16:01:38 +08:00
if(strbuf->length == 0)
2022-09-06 13:24:57 +02:00
return;
uint32_t index = 0;
2021-05-13 12:27:44 +02:00
while(index < strbuf->length && strbuf->chars[index] == c)
++index;
if(index == 0)
return;
2023-07-25 16:01:38 +08:00
if(strbuf->allocated == 0)
{
//static string
strbuf->length -= index;
strbuf->chars += index;
return;
}
2021-04-25 19:12:05 +02:00
memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);
strbuf->length -= index;
strbuf->chars[strbuf->length] = '\0';
}
void ffStrbufTrimRight(FFstrbuf* strbuf, char c)
2021-03-27 18:14:19 +01:00
{
2024-01-06 18:33:57 +08:00
if (strbuf->length == 0)
return;
if (!ffStrbufEndsWithC(strbuf, c))
2022-09-06 13:24:57 +02:00
return;
2024-01-06 18:33:57 +08:00
do
--strbuf->length;
2024-01-06 18:33:57 +08:00
while (ffStrbufEndsWithC(strbuf, c));
2022-09-06 13:24:57 +02:00
2024-01-06 18:33:57 +08:00
if (strbuf->allocated == 0)
2023-07-25 16:01:38 +08:00
{
//static string
ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
return;
}
2023-07-25 16:01:38 +08:00
strbuf->chars[strbuf->length] = '\0';
}
2025-07-05 08:31:21 +08:00
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)
{
2024-01-06 18:33:57 +08:00
if (strbuf->length == 0)
return;
if (!ffStrbufEndsWithFn(strbuf, isspace))
return;
2024-01-06 18:33:57 +08:00
do
--strbuf->length;
2024-01-06 18:33:57 +08:00
while (ffStrbufEndsWithFn(strbuf, isspace));
2024-01-06 18:33:57 +08:00
if (strbuf->allocated == 0)
{
//static string
ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
return;
}
strbuf->chars[strbuf->length] = '\0';
}
2024-07-16 15:39:06 +08:00
bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex)
2021-03-28 13:02:40 +02:00
{
2022-01-12 01:02:14 +01:00
if(startIndex > strbuf->length || startIndex >= endIndex)
2024-07-16 15:39:06 +08:00
return false;
2021-03-28 13:02:40 +02:00
2022-01-12 01:02:14 +01:00
if(endIndex > strbuf->length)
{
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
}
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);
2021-03-28 13:02:40 +02:00
}
void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[])
2021-03-28 13:02:40 +02:00
{
2022-01-12 01:02:14 +01:00
for(uint32_t i = 0; i < numStrings; i++)
ffStrbufRemoveS(strbuf, strings[i]);
2021-03-28 13:02:40 +02:00
}
2021-06-26 15:34:01 +02:00
uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c)
2021-03-28 15:38:52 +02:00
{
2022-09-24 19:22:33 +08:00
assert(start <= strbuf->length);
2021-03-28 15:38:52 +02:00
2022-09-24 19:22:33 +08:00
const char* ptr = (const char*)memchr(strbuf->chars + start, c, strbuf->length - start);
return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length;
2021-07-12 19:01:31 +02:00
}
2022-09-24 19:22:33 +08:00
uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str)
2021-07-12 19:01:31 +02:00
{
2022-09-24 19:22:33 +08:00
assert(start <= strbuf->length);
2021-07-12 19:01:31 +02:00
2022-09-24 19:22:33 +08:00
const char* ptr = strstr(strbuf->chars + start, str);
return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length;
2021-05-05 21:12:42 +02:00
}
2021-06-26 15:34:01 +02:00
uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c)
2021-03-28 13:02:40 +02:00
{
2022-09-24 19:22:33 +08:00
assert(start <= strbuf->length);
2021-06-26 15:34:01 +02:00
//We need to loop one higher than the actual index, because uint32_t is guaranteed to be >= 0, so this statement would always be true
2021-06-26 15:34:01 +02:00
for(uint32_t i = start + 1; i > 0; i--)
2021-03-28 13:02:40 +02:00
{
if(strbuf->chars[i - 1] == c)
return i - 1;
}
return strbuf->length;
}
void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace)
{
2023-07-25 16:01:38 +08:00
if (strbuf->length == 0)
return;
ffStrbufEnsureFree(strbuf, 0);
for (char *current_pos = strchr(strbuf->chars, find); current_pos; current_pos = strchr(current_pos + 1, find))
*current_pos = replace;
}
2024-07-16 15:39:06 +08:00
bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index)
2021-03-28 13:02:40 +02:00
{
2021-04-02 16:21:52 +02:00
if(strbuf->length <= index)
2024-07-16 15:39:06 +08:00
return false;
2021-03-28 13:02:40 +02:00
2023-07-25 16:01:38 +08:00
if(strbuf->allocated == 0)
{
//static string
2025-07-28 15:32:18 +08:00
if (index < strbuf->length)
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
}
2024-07-16 15:39:06 +08:00
bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index)
2021-04-02 16:21:52 +02:00
{
if(index >= strbuf->length)
{
ffStrbufClear(strbuf);
2024-07-16 15:39:06 +08:00
return true;
2021-04-02 16:21:52 +02:00
}
2023-07-25 16:01:38 +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
}
2024-07-16 15:39:06 +08:00
bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c)
2021-05-07 17:10:22 +02:00
{
uint32_t index = ffStrbufFirstIndexC(strbuf, c);
2024-07-16 15:39:06 +08:00
if(index >= strbuf->length)
return false;
ffStrbufSubstrAfter(strbuf, index);
return true;
2021-05-07 17:10:22 +02:00
}
2024-07-16 15:39:06 +08:00
bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str)
2022-01-10 20:20:18 +01:00
{
2022-01-12 01:02:14 +01:00
if(*str == '\0')
2024-07-16 15:39:06 +08:00
return false;
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
2024-07-16 15:39:06 +08:00
if(index >= strbuf->length)
return false;
ffStrbufSubstrAfter(strbuf, index);
return true;
2022-01-10 20:20:18 +01:00
}
2024-07-16 15:39:06 +08:00
bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c)
2021-04-02 16:21:52 +02:00
{
uint32_t index = ffStrbufLastIndexC(strbuf, c);
2024-07-16 15:39:06 +08:00
if(index >= strbuf->length)
return false;
ffStrbufSubstrAfter(strbuf, index);
return true;
2021-04-02 16:21:52 +02:00
}
2025-07-28 15:32:18 +08:00
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;
}
2022-04-21 17:59:26 +02:00
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++;
}
2022-09-24 19:22:33 +08:00
2022-04-21 17:59:26 +02:00
return result;
}
2021-06-26 16:48:15 +02:00
bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end)
{
2022-09-24 23:17:38 +08:00
uint32_t endLength = (uint32_t) strlen(end);
if(ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end))
2021-06-26 16:48:15 +02:00
{
ffStrbufSubstrBefore(strbuf, strbuf->length - endLength);
return true;
}
return false;
}
2024-07-16 15:39:06 +08:00
bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c)
{
2024-07-16 15:39:06 +08:00
if(ffStrbufEndsWithC(strbuf, c))
return false;
ffStrbufAppendC(strbuf, c);
return true;
}
2022-01-12 01:02:14 +01:00
void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file)
2021-04-05 19:25:34 +02:00
{
2022-01-12 01:02:14 +01:00
fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file);
}
void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file)
{
ffStrbufWriteTo(strbuf, file);
fputc('\n', file);
2021-04-05 19:25:34 +02:00
}
2025-08-14 09:47:14 +08:00
double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue)
2022-06-16 14:55:02 +02:00
{
2022-09-24 19:22:33 +08:00
char* str_end;
double result = strtod(strbuf->chars, &str_end);
2025-08-14 09:47:14 +08:00
return str_end == strbuf->chars ? defaultValue : result;
2022-06-16 14:55:02 +02:00
}
uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue)
2022-09-04 22:20:12 +02:00
{
2022-09-24 19:22:33 +08:00
char* str_end;
unsigned long long result = strtoull(strbuf->chars, &str_end, 10);
return str_end == strbuf->chars ? defaultValue : (uint64_t)result;
2022-09-04 22:20:12 +02:00
}
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;
}
2025-08-19 15:00:30 +08:00
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)
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;
if (precision == 0)
value = round(value);
2025-08-19 15:00:30 +08:00
yyjson_val val = {};
unsafe_yyjson_set_double(&val, value);
if (precision > 0)
unsafe_yyjson_set_fp_to_fixed(&val, precision);
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);
assert(end > start);
2025-08-19 15:00:30 +08:00
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)
2025-08-19 15:00:30 +08:00
{
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;
}
2025-08-19 15:00:30 +08:00
}
else
2025-08-19 15:00:30 +08:00
{
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';
}
2025-08-19 15:00:30 +08:00
}
}
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;
}
/**
* @brief Read a line from a FFstrbuf.
*
* @details Behaves like getline(3) but reads from a FFstrbuf.
*
* @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line.
* Can be NULL for the first call.
* @param[in,out] n The pointer to the size of the buffer of lineptr.
* @param[in] buffer The buffer to read from. The buffer must not be a string literal.
*
* @return true if a line has been read, false if the end of the buffer has been reached.
*/
bool ffStrbufGetline(char** lineptr, size_t* n, 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 = '\n';
++*lineptr;
}
if (*lineptr >= pBufferEnd) // empty last line
return false;
size_t remaining = (size_t) (pBufferEnd - *lineptr);
char* ending = memchr(*lineptr, '\n', remaining);
if (ending)
{
*n = (size_t) (ending - *lineptr);
*ending = '\0';
}
else
*n = remaining;
return true;
}
/// @brief Restore the end of a line that was modified by ffStrbufGetline.
/// @warning This function should be called before breaking an ffStrbufGetline loop.
void ffStrbufGetlineRestore(char** lineptr, size_t* n, 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 = '\n';
}
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;
}
2025-05-21 16:40:42 +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.
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, compLength);
if (colon == NULL)
return strcmp(strbuf->chars, p) == 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, compLength);
if (colon == NULL)
return strcasecmp(strbuf->chars, p) == 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;
}