ohmu
·
2026-10-15
1#include "common/FFstrbuf.h"
2#include "common/mallocHelper.h"
3#include "common/strutil.h"
4#include "common/debug.h"
5
6#include <ctype.h>
7#include <inttypes.h>
8#include <math.h>
9
10char* CHAR_NULL_PTR = "";
11
12void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) {
13 strbuf->allocated = allocate;
14
15 if (strbuf->allocated > 0) {
16 strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated);
17 }
18
19 // This will set the length to zero and the null byte.
20 ffStrbufClear(strbuf);
21}
22
23void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
24 char* buffer = nullptr;
25 int len = vasprintf(&buffer, format, arguments);
26 assert(len >= 0);
27
28 ffStrbufInitMoveNS(strbuf, (uint32_t) len, buffer);
29}
30
31// Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this
32// function; the memory will be managed and freed via the associated FFstrbuf.
33void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) {
34 strbuf->length = length;
35 size_t allocSize = ffMallocUsableSize(heapStr);
36 if (allocSize == 0) {
37 allocSize = length + 1;
38 } else if (allocSize > UINT32_MAX) {
39 allocSize = UINT32_MAX;
40 }
41 strbuf->allocated = (uint32_t) allocSize;
42 strbuf->chars = heapStr;
43}
44
45void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...) {
46 va_list arguments;
47 va_start(arguments, format);
48 ffStrbufInitVF(strbuf, format, arguments);
49 va_end(arguments);
50}
51
52FFstrbuf ffStrbufCreateF(const char* format, ...) {
53 FFstrbuf strbuf;
54
55 va_list arguments;
56 va_start(arguments, format);
57 ffStrbufInitVF(&strbuf, format, arguments);
58 va_end(arguments);
59
60 return strbuf;
61}
62
63void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) {
64 uint32_t allocate;
65 if (__builtin_expect(__builtin_uadd_overflow(strbuf->length, free, &allocate), false)) {
66 FF_DEBUG("Error: Integer overflow when calculating allocation size. Aborting");
67 abort();
68 }
69
70 if (allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) { // `<` for null terminator
71 allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC;
72 } else {
73 if (__builtin_expect(allocate > (UINT32_MAX >> 1), false)) {
74 // User tried to allocate more than 2GB of memory, which exceeds the maximum size supported by FFstrbuf.
75 // This is likely an error or an attempt to exploit the program. Abort to prevent potential issues.
76 FF_DEBUG("Error: Attempted to allocate %" PRIu32 " bytes more than 2GB of memory in FFstrbuf. Aborting", allocate);
77 abort();
78 }
79
80 // Round up to the next power of 2.
81 // If the value is already a power of 2, it will be rounded up to the next power of 2.
82 // allocate = __builtin_stdc_bit_ceil(allocate + 1); // Currently generates worse asm code
83 allocate = 1U << (32 - __builtin_clz(allocate));
84 }
85
86 if (strbuf->allocated == 0) {
87 char* newbuf = malloc(sizeof(*strbuf->chars) * allocate);
88 if (strbuf->length == 0) {
89 *newbuf = '\0';
90 } else {
91 memcpy(newbuf, strbuf->chars, strbuf->length + 1);
92 }
93 strbuf->chars = newbuf;
94 } else {
95 strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate);
96 }
97
98 strbuf->allocated = allocate;
99}
100
101// Ensure that at least `free` bytes are available in the buffer besides the current length
102// for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL)
103// This function ensures a dynamic buffer is allocated even if free == 0
104void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) {
105 uint32_t newCap;
106
107 if (strbuf->allocated == 0) {
108 assert(strbuf->length < UINT32_MAX - 1); // We don't use static strings with length >= UINT32_MAX - 1, so this should never happen
109 if (__builtin_expect(__builtin_uadd_overflow(strbuf->length + 1, free, &newCap), false)) {
110 FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting");
111 abort();
112 }
113
114 char* newbuf = malloc(sizeof(*strbuf->chars) * newCap);
115 if (strbuf->length == 0) {
116 *newbuf = '\0';
117 } else {
118 memcpy(newbuf, strbuf->chars, strbuf->length + 1);
119 }
120 strbuf->chars = newbuf;
121 } else {
122 uint32_t oldFree = ffStrbufGetFree(strbuf);
123 if (oldFree >= free) {
124 return;
125 }
126
127 if (__builtin_expect(__builtin_uadd_overflow(strbuf->allocated, free - oldFree, &newCap), false)) {
128 FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting");
129 abort();
130 }
131 strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap);
132 }
133
134 strbuf->allocated = newCap;
135}
136
137void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int)) {
138 if (value == nullptr) {
139 return;
140 }
141
142 // Ensure capacity > 0 or the modification below will fail
143 uint32_t length = (uint32_t) strlen(value);
144 if (length == 0) {
145 return;
146 }
147
148 ffStrbufEnsureFree(strbuf, length);
149 for (uint32_t i = 0; value[i] != '\0'; i++) {
150 strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]);
151 }
152 strbuf->chars[strbuf->length] = '\0';
153}
154
155void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
156 va_list copy;
157 va_copy(copy, arguments);
158
159 uint32_t free = ffStrbufGetFree(strbuf);
160 int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments);
161
162 if (written > 0 && (uint32_t) written > free) {
163 ffStrbufEnsureFreeNoCheck(strbuf, (uint32_t) written);
164 written = vsnprintf(strbuf->chars + strbuf->length, (uint32_t) written + 1, format, copy);
165 }
166
167 va_end(copy);
168
169 if (written > 0) {
170 strbuf->length += (uint32_t) written;
171 }
172}
173
174const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until) {
175 if (value == nullptr) {
176 return nullptr;
177 }
178
179 const char* end = strchr(value, until);
180 if (end == nullptr) {
181 ffStrbufAppendS(strbuf, value);
182 } else {
183 ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value);
184 }
185 return end;
186}
187
188void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) {
189 va_list arguments;
190 va_start(arguments, format);
191
192 if (strbuf->allocated == 0) {
193 ffStrbufInitVF(strbuf, format, arguments);
194 va_end(arguments);
195 return;
196 }
197
198 ffStrbufClear(strbuf);
199 ffStrbufAppendVF(strbuf, format, arguments);
200 va_end(arguments);
201}
202
203void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) {
204 va_list arguments;
205 va_start(arguments, format);
206 ffStrbufAppendVF(strbuf, format, arguments);
207 va_end(arguments);
208}
209
210void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
211 if (value == nullptr || length == 0) {
212 return;
213 }
214
215 ffStrbufEnsureFree(strbuf, length);
216 memmove(strbuf->chars + length, strbuf->chars, strbuf->length + 1); // + 1 for the null byte
217 memcpy(strbuf->chars, value, length);
218 strbuf->length += length;
219}
220
221void ffStrbufPrependC(FFstrbuf* strbuf, char c) {
222 ffStrbufEnsureFree(strbuf, 1);
223 memmove(strbuf->chars + 1, strbuf->chars, strbuf->length + 1); // + 1 for the null byte
224 strbuf->chars[0] = c;
225 strbuf->length += 1;
226}
227
228void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
229 if (length == 0) {
230 ffStrbufClear(strbuf);
231 return;
232 }
233
234 if (strbuf->allocated <= length) {
235 char* newBuf = malloc(sizeof(char) * (length + 1));
236 memcpy(newBuf, value, length);
237 if (strbuf->allocated > 0) {
238 free(strbuf->chars);
239 }
240 strbuf->chars = newBuf;
241 strbuf->allocated = length + 1;
242 } else {
243 memmove(strbuf->chars, value, length);
244 }
245
246 strbuf->length = length;
247 strbuf->chars[length] = '\0';
248}
249
250void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) {
251 // `value` is non-null per the `nonnull(2)` contract; the aliasing check cannot be expressed by an attribute
252 assert(value != strbuf);
253
254 if (value->length == 0) {
255 ffStrbufClear(strbuf);
256 return;
257 }
258
259 if (value->allocated == 0) // static string
260 {
261 if (strbuf->allocated != 0) {
262 free(strbuf->chars);
263 strbuf->allocated = 0;
264 }
265 strbuf->chars = value->chars;
266 strbuf->length = value->length;
267 return;
268 }
269 ffStrbufSetNS(strbuf, value->length, value->chars);
270}
271
272void ffStrbufTrimLeft(FFstrbuf* strbuf, char c) {
273 if (strbuf->length == 0) {
274 return;
275 }
276
277 uint32_t index = 0;
278 while (index < strbuf->length && strbuf->chars[index] == c) {
279 ++index;
280 }
281
282 if (index == 0) {
283 return;
284 }
285
286 if (strbuf->allocated == 0) {
287 // static string
288 strbuf->length -= index;
289 strbuf->chars += index;
290 return;
291 }
292
293 memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);
294 strbuf->length -= index;
295 strbuf->chars[strbuf->length] = '\0';
296}
297
298void ffStrbufTrimRight(FFstrbuf* strbuf, char c) {
299 if (strbuf->length == 0) {
300 return;
301 }
302
303 if (!ffStrbufEndsWithC(strbuf, c)) {
304 return;
305 }
306
307 do {
308 --strbuf->length;
309 } while (ffStrbufEndsWithC(strbuf, c));
310
311 if (strbuf->allocated == 0) {
312 // static string
313 ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
314 return;
315 }
316
317 strbuf->chars[strbuf->length] = '\0';
318}
319
320void ffStrbufTrimLeftSpace(FFstrbuf* strbuf) {
321 if (strbuf->length == 0) {
322 return;
323 }
324
325 uint32_t index = 0;
326 while (index < strbuf->length && isspace(strbuf->chars[index])) {
327 ++index;
328 }
329
330 if (index == 0) {
331 return;
332 }
333
334 if (strbuf->allocated == 0) {
335 // static string
336 strbuf->length -= index;
337 strbuf->chars += index;
338 return;
339 }
340
341 memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);
342 strbuf->length -= index;
343 strbuf->chars[strbuf->length] = '\0';
344}
345
346void ffStrbufTrimRightSpace(FFstrbuf* strbuf) {
347 if (strbuf->length == 0) {
348 return;
349 }
350
351 if (!ffStrbufEndsWithFn(strbuf, isspace)) {
352 return;
353 }
354
355 do {
356 --strbuf->length;
357 } while (ffStrbufEndsWithFn(strbuf, isspace));
358
359 if (strbuf->allocated == 0) {
360 // static string
361 ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
362 return;
363 }
364
365 strbuf->chars[strbuf->length] = '\0';
366}
367
368bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex) {
369 if (startIndex > strbuf->length || startIndex >= endIndex) {
370 return false;
371 }
372
373 if (endIndex > strbuf->length) {
374 ffStrbufSubstrBefore(strbuf, startIndex);
375 return true;
376 }
377
378 ffStrbufEnsureFree(strbuf, 0);
379 memmove(strbuf->chars + startIndex, strbuf->chars + endIndex, strbuf->length - endIndex);
380 strbuf->length -= (endIndex - startIndex);
381 strbuf->chars[strbuf->length] = '\0';
382 return true;
383}
384
385void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str) {
386 uint32_t stringLength = (uint32_t) strlen(str);
387
388 for (uint32_t i = ffStrbufNextIndexS(strbuf, 0, str); i < strbuf->length; i = ffStrbufNextIndexS(strbuf, i, str)) {
389 ffStrbufRemoveSubstr(strbuf, i, i + stringLength);
390 }
391}
392
393void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]) {
394 for (uint32_t i = 0; i < numStrings; i++) {
395 ffStrbufRemoveS(strbuf, strings[i]);
396 }
397}
398
399void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace) {
400 if (strbuf->length == 0) {
401 return;
402 }
403
404 ffStrbufEnsureFree(strbuf, 0);
405 for (
406 char* current_pos = memchr(strbuf->chars, find, strbuf->length);
407 current_pos;
408 current_pos = memchr(
409 current_pos + 1,
410 find,
411 strbuf->length - (uint32_t) (current_pos + 1 - strbuf->chars))) {
412 *current_pos = replace;
413 }
414}
415
416bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) {
417 if (strbuf->length <= index) {
418 return false;
419 }
420
421 if (strbuf->allocated == 0) {
422 // static string
423 ffStrbufInitNS(strbuf, index, strbuf->chars);
424 return true;
425 }
426
427 strbuf->length = index;
428 strbuf->chars[strbuf->length] = '\0';
429 return true;
430}
431
432bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index) {
433 if (index >= strbuf->length) {
434 ffStrbufClear(strbuf);
435 return true;
436 }
437
438 if (strbuf->allocated == 0) {
439 // static string
440 strbuf->length -= index + 1;
441 strbuf->chars += index + 1;
442 return true;
443 }
444
445 memmove(strbuf->chars, strbuf->chars + index + 1, strbuf->length - index - 1);
446 strbuf->length -= (index + 1);
447 strbuf->chars[strbuf->length] = '\0';
448 return true;
449}
450
451bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c) {
452 uint32_t index = ffStrbufFirstIndexC(strbuf, c);
453 if (index >= strbuf->length) {
454 return false;
455 }
456 ffStrbufSubstrAfter(strbuf, index);
457 return true;
458}
459
460bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str) {
461 if (*str == '\0') {
462 return false;
463 }
464
465 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
466 if (index >= strbuf->length) {
467 return false;
468 }
469
470 ffStrbufSubstrAfter(strbuf, index);
471 return true;
472}
473
474bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c) {
475 uint32_t index = ffStrbufLastIndexC(strbuf, c);
476 if (index >= strbuf->length) {
477 return false;
478 }
479
480 ffStrbufSubstrAfter(strbuf, index);
481 return true;
482}
483
484bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end) {
485 if (__builtin_expect(start >= end, false)) {
486 ffStrbufClear(strbuf);
487 return false;
488 }
489
490 if (__builtin_expect(start == 0, false)) {
491 return ffStrbufSubstrBefore(strbuf, end);
492 }
493 if (__builtin_expect(end >= strbuf->length, false)) {
494 return ffStrbufSubstrAfter(strbuf, start - 1);
495 }
496
497 uint32_t len = end - start;
498 ffStrbufEnsureFixedLengthFree(strbuf, len); // In case of static string
499 memmove(strbuf->chars, strbuf->chars + start, len);
500
501 strbuf->length = len;
502 strbuf->chars[len] = '\0';
503 return true;
504}
505
506uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c) {
507 uint32_t result = 0;
508 for (uint32_t i = 0; i < strbuf->length; i++) {
509 if (strbuf->chars[i] == c) {
510 result++;
511 }
512 }
513
514 return result;
515}
516
517bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end) {
518 uint32_t endLength = (uint32_t) strlen(end);
519 if (ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end)) {
520 ffStrbufSubstrBefore(strbuf, strbuf->length - endLength);
521 return true;
522 }
523
524 return false;
525}
526
527bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c) {
528 if (ffStrbufEndsWithC(strbuf, c)) {
529 return false;
530 }
531
532 ffStrbufAppendC(strbuf, c);
533 return true;
534}
535
536void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value) {
537 ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number
538 char* start = strbuf->chars + strbuf->length;
539
540 yyjson_val val = {};
541 unsafe_yyjson_set_sint(&val, value);
542 char* end = yyjson_write_number(&val, start);
543
544 assert(end != nullptr);
545
546 strbuf->length += (uint32_t) (end - start);
547}
548
549void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value) {
550 ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number
551 char* start = strbuf->chars + strbuf->length;
552
553 yyjson_val val = {};
554 unsafe_yyjson_set_uint(&val, value);
555 char* end = yyjson_write_number(&val, start);
556
557 assert(end != nullptr);
558
559 strbuf->length += (uint32_t) (end - start);
560}
561
562void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros) {
563 assert(precision <= 15); // yyjson_write_number supports up to 15 digits after the decimal point
564
565 ffStrbufEnsureFree(strbuf, 40); // Required by yyjson_write_number
566 char* start = strbuf->chars + strbuf->length;
567
568 if (precision == 0) {
569 value = round(value);
570 }
571 yyjson_val val = {};
572 unsafe_yyjson_set_double(&val, value);
573 if (precision > 0) {
574 unsafe_yyjson_set_fp_to_fixed(&val, precision);
575 }
576
577 // Write at most <precision> digits after the decimal point; doesn't append trailing zeros
578 char* end = yyjson_write_number(&val, start);
579
580 assert(end > start);
581
582 strbuf->length += (uint32_t) (end - start);
583
584 if (__builtin_expect(value > 1e21 || value < -1e21, false)) {
585 // If the value is too large, yyjson_write_number will write it in scientific notation
586 return;
587 }
588
589 if (trailingZeros) {
590 if (precision > 1) {
591 for (char* p = end - 1; *p != '.' && p > start; --p) {
592 --precision;
593 }
594 if (precision > 0) {
595 ffStrbufAppendNC(strbuf, (uint32_t) precision, '0');
596 }
597 } else if (precision == 0 || (precision < 0 && end[-1] == '0')) {
598 goto removeDecimalPoint;
599 }
600 } else {
601 if (end[-1] == '0') {
602 removeDecimalPoint:
603 // yyjson always appends ".0" to make it a float point number. We need to remove it
604 strbuf->length -= 2;
605 strbuf->chars[strbuf->length] = '\0';
606 }
607 }
608}
609
610void ffStrbufUpperCase(FFstrbuf* strbuf) {
611 for (uint32_t i = 0; i < strbuf->length; ++i) {
612 strbuf->chars[i] = (char) toupper(strbuf->chars[i]);
613 }
614}
615
616void ffStrbufLowerCase(FFstrbuf* strbuf) {
617 for (uint32_t i = 0; i < strbuf->length; ++i) {
618 strbuf->chars[i] = (char) tolower(strbuf->chars[i]);
619 }
620}
621
622void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) {
623 if (num == 0) {
624 return;
625 }
626 if (index >= strbuf->length) {
627 index = strbuf->length;
628 }
629
630 ffStrbufEnsureFree(strbuf, num);
631 memmove(strbuf->chars + index + num, strbuf->chars + index, strbuf->length - index + 1);
632 memset(&strbuf->chars[index], c, num);
633 strbuf->length += num;
634}
635
636bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
637 assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
638 assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
639
640 const char* pBufferEnd = buffer->chars + buffer->length;
641 if (!*lineptr) {
642 *lineptr = buffer->chars;
643 } else {
644 *lineptr += *n;
645 if (*lineptr >= pBufferEnd) { // non-empty last line
646 return false;
647 }
648 **lineptr = delimiter;
649 ++*lineptr;
650 }
651 if (*lineptr >= pBufferEnd) { // empty last line
652 return false;
653 }
654
655 size_t remaining = (size_t) (pBufferEnd - *lineptr);
656 char* ending = memchr(*lineptr, delimiter, remaining);
657 if (ending) {
658 *n = (size_t) (ending - *lineptr);
659 *ending = '\0';
660 } else {
661 *n = remaining;
662 }
663 return true;
664}
665
666void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
667 assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
668 assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
669
670 if (!*lineptr) {
671 return;
672 }
673
674 *lineptr += *n;
675 if (*lineptr < buffer->chars + buffer->length) {
676 **lineptr = delimiter;
677 }
678}
679
680bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) {
681 if (strbuf->allocated == 0) {
682 return false; // Doesn't work with static strings
683 }
684
685 bool changed = false;
686 for (uint32_t i = 0; i < strbuf->length; i++) {
687 if (strbuf->chars[i] != ' ') {
688 continue;
689 }
690
691 i++;
692 uint32_t j = i;
693 for (; j < strbuf->length && strbuf->chars[j] == ' '; j++);
694
695 if (j == i) {
696 continue;
697 }
698 memmove(&strbuf->chars[i], &strbuf->chars[j], strbuf->length - j + 1);
699 strbuf->length -= j - i;
700 changed = true;
701 }
702
703 return changed;
704}
705
706/// @brief Check if a separated string (comp) contains a substring (strbuf).
707/// @param strbuf The substring to check.
708/// @param compLength The length of the separated string to check.
709/// @param comp The separated string to check.
710/// @param separator The separator character.
711///
712/// Empty-string handling:
713/// - If `strbuf` is empty, it matches only if `comp` contains an empty segment
714/// (e.g. "abc::def", "abc:", ":abc") or `comp` itself is empty.
715/// - If `comp` is empty (compLength == 0), it matches only if `strbuf` is also empty.
716bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
717 if (__builtin_expect(compLength == 0, false)) {
718 return strbuf->length == 0;
719 }
720
721 for (const char* p = comp; p <= comp + compLength;) {
722 const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
723 if (colon == nullptr) {
724 uint32_t remainingLen = (uint32_t) (comp + compLength - p);
725 return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0;
726 }
727
728 uint32_t substrLength = (uint32_t) (colon - p);
729 if (strbuf->length == substrLength && memcmp(strbuf->chars, p, substrLength) == 0) {
730 return true;
731 }
732
733 p = colon + 1;
734 }
735
736 return false;
737}
738
739/// @brief Case insensitive version of ffStrbufMatchSeparatedNS.
740bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
741 if (__builtin_expect(compLength == 0, false)) {
742 return strbuf->length == 0;
743 }
744
745 for (const char* p = comp; p <= comp + compLength;) {
746 const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
747 if (colon == nullptr) {
748 uint32_t remainingLen = (uint32_t) (comp + compLength - p);
749 return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0;
750 }
751
752 uint32_t substrLength = (uint32_t) (colon - p);
753 if (strbuf->length == substrLength && strncasecmp(strbuf->chars, p, substrLength) == 0) {
754 return true;
755 }
756
757 p = colon + 1;
758 }
759
760 return false;
761}
762
763int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) {
764 if (codepoint <= 0x7F) {
765 ffStrbufAppendC(strbuf, (char) codepoint);
766 return 1;
767 } else if (codepoint <= 0x7FF) {
768 ffStrbufAppendNS(strbuf, 2, (char[]) { (char) (0xC0 | (codepoint >> 6)), (char) (0x80 | (codepoint & 0x3F)) });
769 return 2;
770 } else if (codepoint <= 0xFFFF) {
771 ffStrbufAppendNS(strbuf, 3, (char[]) { (char) (0xE0 | (codepoint >> 12)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) });
772 return 3;
773 } else if (codepoint <= 0x10FFFF) {
774 ffStrbufAppendNS(strbuf, 4, (char[]) { (char) (0xF0 | (codepoint >> 18)), (char) (0x80 | ((codepoint >> 12) & 0x3F)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) });
775 return 4;
776 }
777
778 ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER
779 return 1;
780}
781
782/// @brief Check if a separated string (strbuf) contains a substring (comp).
783/// @param strbuf The separated to check.
784/// @param compLength The length of the separated string to check.
785/// @param comp The substring to check.
786/// @param separator The separator character.
787///
788/// Empty-string handling is symmetric to ffStrbufMatchSeparatedNS
789bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
790 if (__builtin_expect(strbuf->length == 0, false)) {
791 return compLength == 0; // An empty list contains only the empty segment
792 }
793
794 uint32_t startIndex = 0;
795 while (startIndex <= strbuf->length) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
796 uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
797
798 uint32_t folderLength = colonIndex - startIndex;
799 if (folderLength == compLength && memcmp(strbuf->chars + startIndex, comp, compLength) == 0) {
800 return true;
801 }
802
803 startIndex = colonIndex + 1;
804 }
805
806 return false;
807}
808
809bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
810 if (__builtin_expect(strbuf->length == 0, false)) {
811 return compLength == 0; // An empty list contains only the empty segment
812 }
813
814 uint32_t startIndex = 0;
815 while (startIndex <= strbuf->length) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
816 uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
817
818 uint32_t folderLength = colonIndex - startIndex;
819 if (folderLength == compLength && strncasecmp(strbuf->chars + startIndex, comp, compLength) == 0) {
820 return true;
821 }
822
823 startIndex = colonIndex + 1;
824 }
825
826 return false;
827}
828
829bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) {
830 if (strbuf->length < 4) {
831 return false;
832 }
833
834 // Static string must be converted first.
835 assert(strbuf->allocated > 0);
836
837 bool changed = false;
838 uint32_t read = 0;
839 uint32_t write = 0;
840
841 while (read < strbuf->length) {
842 if (
843 read + 3 < strbuf->length &&
844 strbuf->chars[read] == '\\' &&
845 strbuf->chars[read + 1] == 'x') {
846 int8_t hi = ffHexCharToInt(strbuf->chars[read + 2]);
847 int8_t lo = ffHexCharToInt(strbuf->chars[read + 3]);
848 if (hi >= 0 && lo >= 0) {
849 strbuf->chars[write++] = (char) ((hi << 4) | lo);
850 read += 4;
851 changed = true;
852 continue;
853 }
854 }
855
856 strbuf->chars[write++] = strbuf->chars[read++];
857 }
858
859 strbuf->length = write;
860 strbuf->chars[write] = '\0';
861 return changed;
862}