ohmu
·
2026-10-15
1#pragma once
2
3#include <stdint.h>
4#include <stdarg.h>
5#include <stdbool.h>
6#include <stdio.h>
7#include <string.h>
8#include <stdlib.h>
9#include <assert.h>
10#include "common/memrchr.h"
11#include "common/arrutil.h"
12
13#ifdef FF_USE_SYSTEM_YYJSON
14 #include <yyjson.h>
15#else
16 #include "3rdparty/yyjson/yyjson.h"
17#endif
18
19#ifdef _WIN32
20// #include <shlwapi.h>
21__stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch);
22 #define strcasestr StrStrIA
23#endif
24
25#define FASTFETCH_STRBUF_DEFAULT_ALLOC 32
26
27// ---------------------------------------------------------------------------------------------
28// Contract attributes used throughout this header:
29//
30// `gnu::pure` marks a function that reads memory (through its arguments and through globals) but
31// writes nothing observable, so the compiler may cache and reorder calls to it. It is applied to
32// the comparison / search accessors, and deliberately NOT to:
33// * the `To*` converters -- `strtod` / `strtoull` / `strtoll` write `errno`;
34// * `ffStrbufEndsWithFn` -- the caller-supplied `fn` may have side effects;
35// * `ffStrbufWriteTo` / `ffStrbufPutTo` -- they write to a `FILE*`.
36//
37// `gnu::nonnull(N)` is added wherever the function dereferences argument N unconditionally. The
38// two cannot be combined with a null check: clang reports `-Wtautological-pointer-compare` /
39// `-Wpointer-bool-conversion` (both in `-Wall`) when a `nonnull` parameter is compared against
40// null, so a redundant `assert(p != nullptr)` is dropped in favour of the attribute. Asserts that
41// state something `nonnull` cannot express -- pointer aliasing (`value != strbuf`), index ranges
42// (`start <= strbuf->length`), or a non-pointer invariant -- are kept.
43//
44// `nodiscard` is NOT applied to the "was the buffer modified" boolean returns
45// (`ffStrbufSubstr*`, `ffStrbufRemoveSubstr`, `ffStrbufEnsureEndsWithC`, ...). Ignoring that
46// result is a normal, intended use of those functions -- `ffStrbufSubstrBefore` alone has 153 call
47// sites and ~147 of them discard the result -- so `nodiscard` would only produce noise.
48// ---------------------------------------------------------------------------------------------
49
50// static string (allocated == 0), chars points to a string literal
51// dynamic string (allocated > 0), chars points to a heap allocated buffer
52typedef struct FFstrbuf {
53 uint32_t allocated;
54 uint32_t length;
55 char* chars;
56} FFstrbuf;
57
58[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf);
59[[gnu::nonnull(1)]] void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate);
60[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments);
61[[gnu::nonnull(1, 3)]] void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr);
62[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...);
63[[gnu::format(printf, 1, 2), gnu::nonnull(1), nodiscard]] FFstrbuf ffStrbufCreateF(const char* format, ...);
64
65[[gnu::nonnull(1)]] void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free);
66[[gnu::nonnull(1)]] void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free);
67
68[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value);
69[[gnu::nonnull(1, 3)]] void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int));
70[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...);
71[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments);
72// Returns the pointer to the terminator, or nullptr if `value` was nullptr; callers are free to
73// ignore it, so not `nodiscard`.
74[[gnu::nonnull(1)]] const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until);
75
76[[gnu::nonnull(1)]] void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value);
77[[gnu::nonnull(1)]] void ffStrbufPrependC(FFstrbuf* strbuf, char c);
78
79[[gnu::nonnull(1)]] void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c);
80
81// Clear the content of strbuf and set new value
82// NOTE: Unlike ffStrbufAppend*, ffStrbufSet* functions may NOT reserve extra space
83[[gnu::nonnull(1, 2)]] void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value);
84[[gnu::nonnull(1, 3)]] void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value);
85[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...);
86
87[[gnu::nonnull(1)]] void ffStrbufTrimLeft(FFstrbuf* strbuf, char c);
88[[gnu::nonnull(1)]] void ffStrbufTrimRight(FFstrbuf* strbuf, char c);
89[[gnu::nonnull(1)]] void ffStrbufTrimLeftSpace(FFstrbuf* strbuf);
90[[gnu::nonnull(1)]] void ffStrbufTrimRightSpace(FFstrbuf* strbuf);
91
92[[gnu::nonnull(1)]] bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex);
93[[gnu::nonnull(1, 2)]] void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str);
94// `strings` is only dereferenced when `numStrings > 0`, so it is intentionally not `nonnull(3)`
95[[gnu::nonnull(1)]] void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]);
96
97[[gnu::nonnull(1)]] void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace);
98
99// Returns true if the strbuf is modified
100[[gnu::nonnull(1)]] bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index);
101[[gnu::nonnull(1)]] bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index
102[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c);
103[[gnu::nonnull(1, 2)]] bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str);
104[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c);
105[[gnu::nonnull(1)]] bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end);
106
107[[gnu::nonnull(1), gnu::pure, nodiscard]] uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c);
108
109[[gnu::nonnull(1, 2)]] bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end);
110
111[[gnu::nonnull(1)]] bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c);
112
113[[gnu::nonnull(1)]] void ffStrbufUpperCase(FFstrbuf* strbuf);
114[[gnu::nonnull(1)]] void ffStrbufLowerCase(FFstrbuf* strbuf);
115
116// Function alters the buffer to extract lines or delimited segments (replaces the delimiter with '\0')
117// so that buffer MUST be heap allocated (NOT a static string)
118// `lineptr` must be `nullptr` and `n` MUST be `0` for the first call
119// Caller MUST NOT free `*lineptr`
120[[gnu::nonnull(1, 2, 4)]] bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
121[[gnu::nonnull(1, 2, 4)]] void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
122
123/**
124 * @brief Read a line from a FFstrbuf.
125 *
126 * @details Behaves like getline(3) but reads from a FFstrbuf.
127 *
128 * @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line
129 (points to buffer's internal memory address to avoid memory allocation and copy).
130 MUST NOT be freed by the caller, unlike `getline(3)`.
131 * MUST be nullptr for the first call.
132 * @param[in,out] n The pointer to the size of the buffer of lineptr.
133 MUST be 0 for the first call.
134 * @param[in] buffer The buffer to read from.
135 MUST be heap allocated (NOT a static string).
136 *
137 * @return true if a line has been read, false if the end of the buffer has been reached.
138 */
139[[gnu::nonnull(1, 2, 3), nodiscard]] static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) {
140 return ffStrbufGetdelim(lineptr, n, '\n', buffer);
141}
142/**
143 * @brief Restore the end of a line that was modified by ffStrbufGetline.
144 * @warning This function should be called before breaking an ffStrbufGetline loop if `buffer` will be used later.
145 */
146[[gnu::nonnull(1, 2, 3)]] static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) {
147 ffStrbufGetdelimRestore(lineptr, n, '\n', buffer);
148}
149[[gnu::nonnull(1)]] bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf);
150// `comp` is only dereferenced when `compLength > 0` in the first pair, and is never dereferenced
151// when `strbuf` is empty in the second pair, so neither takes `nonnull(3)`
152[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
153[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
154[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
155[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
156
157[[gnu::nonnull(1)]] int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint);
158
159[[gnu::nonnull(1)]] void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value);
160[[gnu::nonnull(1)]] void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value);
161// Appends a double value to the string buffer with the specified precision (0~15).
162// if `precision < 0`, let yyjson decide the precision
163[[gnu::nonnull(1)]] void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros);
164
165[[nodiscard]] static inline FFstrbuf ffStrbufCreateA(uint32_t allocate) {
166 FFstrbuf strbuf;
167 ffStrbufInitA(&strbuf, allocate);
168 return strbuf;
169}
170
171[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) {
172 if (src->allocated == 0) { // static string
173 *strbuf = *src;
174 } else {
175 ffStrbufInitA(strbuf, src->allocated);
176 ffStrbufAppend(strbuf, src);
177 }
178}
179
180[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) {
181 FFstrbuf strbuf;
182 ffStrbufInitCopy(&strbuf, src);
183 return strbuf;
184}
185
186// Move the content of `src` into `strbuf`, and left `src` empty
187[[gnu::nonnull(1)]] static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) {
188 if (src) {
189 *strbuf = *src;
190 ffStrbufInit(src);
191 } else {
192 ffStrbufInit(strbuf);
193 }
194}
195
196[[nodiscard]] static inline FFstrbuf ffStrbufCreateMove(FFstrbuf* src) {
197 FFstrbuf strbuf;
198 ffStrbufInitMove(&strbuf, src);
199 return strbuf;
200}
201
202[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) {
203 ffStrbufInitMoveNS(strbuf, (uint32_t) strlen(heapStr), heapStr);
204}
205
206// Despite the name, this function resets strbuf to the initial/unallocated state
207[[gnu::nonnull(1)]] static inline void ffStrbufDestroy(FFstrbuf* strbuf) {
208 if (strbuf->allocated > 0) {
209 free(strbuf->chars);
210 }
211
212 ffStrbufInit(strbuf);
213}
214
215[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) {
216 if (strbuf->allocated == 0) {
217 return 0;
218 }
219
220 return strbuf->allocated - strbuf->length - 1; // - 1 for the null byte
221}
222
223[[gnu::nonnull(1)]] static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) {
224 if (__builtin_expect(free == 0, false)) {
225 if (__builtin_expect(!(strbuf->allocated == 0 && strbuf->length > 0), true)) {
226 return;
227 }
228 } else {
229 if (__builtin_expect(ffStrbufGetFree(strbuf) >= free, true)) {
230 return;
231 }
232 }
233
234 ffStrbufEnsureFreeNoCheck(strbuf, free);
235}
236
237
238[[gnu::nonnull(1)]] static inline void ffStrbufClear(FFstrbuf* strbuf) {
239 extern char* CHAR_NULL_PTR;
240
241 if (strbuf->allocated == 0) {
242 strbuf->chars = CHAR_NULL_PTR;
243 } else {
244 strbuf->chars[0] = '\0';
245 }
246
247 strbuf->length = 0;
248}
249
250[[gnu::nonnull(1)]] static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) {
251 ffStrbufEnsureFree(strbuf, 1);
252 strbuf->chars[strbuf->length++] = c;
253 strbuf->chars[strbuf->length] = '\0';
254}
255
256[[gnu::nonnull(1)]] static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) {
257 if (__builtin_expect(num == 0, false)) {
258 return;
259 }
260 ffStrbufEnsureFree(strbuf, num);
261
262 memset(&strbuf->chars[strbuf->length], c, num);
263 strbuf->length += num;
264 strbuf->chars[strbuf->length] = '\0';
265}
266
267[[gnu::nonnull(1)]] static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
268 if (__builtin_expect(value == nullptr || length == 0, false)) {
269 return;
270 }
271 ffStrbufEnsureFree(strbuf, length);
272
273 memcpy(&strbuf->chars[strbuf->length], value, length);
274 strbuf->length += length;
275 strbuf->chars[strbuf->length] = '\0';
276}
277
278[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) {
279 assert(value != strbuf);
280 if (value == nullptr) {
281 return;
282 }
283 ffStrbufAppendNS(strbuf, value->length, value->chars);
284}
285
286[[gnu::nonnull(1)]] static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) {
287 strbuf->length = (uint32_t) strlen(strbuf->chars);
288}
289
290// `value` may be null (clears the buffer); `strbuf` may not
291[[gnu::nonnull(1)]] static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) {
292 if (value == nullptr) {
293 ffStrbufClear(strbuf);
294 } else {
295 ffStrbufSetNS(strbuf, (uint32_t) strlen(value), value);
296 }
297}
298
299[[gnu::nonnull(1)]] static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
300 if (yyjson_is_str(jsonVal)) {
301 ffStrbufSetNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));
302 return true;
303 }
304
305 ffStrbufClear(strbuf);
306 return false;
307}
308
309[[gnu::nonnull(1)]] static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) {
310 if (value == nullptr) {
311 return;
312 }
313 ffStrbufAppendNS(strbuf, (uint32_t) strlen(value), value);
314}
315
316// Returns whether `jsonVal` was a string. Callers routinely pre-check with `yyjson_is_str`, so the
317// result is not `nodiscard`.
318static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
319 if (yyjson_is_str(jsonVal)) {
320 ffStrbufAppendNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));
321 return true;
322 }
323 return false;
324}
325
326[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf) {
327 extern char* CHAR_NULL_PTR;
328 strbuf->allocated = strbuf->length = 0;
329 strbuf->chars = CHAR_NULL_PTR;
330}
331
332[[nodiscard]] static inline FFstrbuf ffStrbufCreate(void) {
333 FFstrbuf strbuf;
334 ffStrbufInit(&strbuf);
335 return strbuf;
336}
337
338static inline void ffStrbufInitStatic(FFstrbuf* strbuf, const char* str) {
339 ffStrbufInit(strbuf);
340 if (!str) {
341 return;
342 }
343
344 strbuf->allocated = 0;
345 strbuf->length = (uint32_t) strlen(str);
346 strbuf->chars = (char*) str;
347}
348
349[[nodiscard]] static inline FFstrbuf ffStrbufCreateStatic(const char* str) {
350 FFstrbuf strbuf;
351 ffStrbufInitStatic(&strbuf, str);
352 return strbuf;
353}
354
355[[gnu::nonnull(1)]] static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) {
356 if (strbuf->allocated > 0) {
357 free(strbuf->chars);
358 }
359
360 if (value != nullptr) {
361 ffStrbufInitStatic(strbuf, value);
362 } else {
363 ffStrbufInit(strbuf);
364 }
365}
366
367[[gnu::nonnull(1)]] static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) {
368 ffStrbufInit(strbuf);
369 ffStrbufAppendNS(strbuf, length, str);
370}
371
372[[nodiscard]] static inline FFstrbuf ffStrbufCreateNS(uint32_t length, const char* str) {
373 FFstrbuf strbuf;
374 ffStrbufInitNS(&strbuf, length, str);
375 return strbuf;
376}
377
378// Returns whether `jsonVal` was a string; not `nodiscard`, same reason as ffStrbufAppendJsonVal
379static inline bool ffStrbufInitJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
380 ffStrbufInit(strbuf);
381 return ffStrbufAppendJsonVal(strbuf, jsonVal);
382}
383
384[[gnu::nonnull(1)]] static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) {
385 ffStrbufInit(strbuf);
386 ffStrbufAppendS(strbuf, str);
387}
388
389[[nodiscard]] static inline FFstrbuf ffStrbufCreateS(const char* str) {
390 FFstrbuf strbuf;
391 ffStrbufInitS(&strbuf, str);
392 return strbuf;
393}
394
395[[gnu::nonnull(1)]] static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) {
396 if (value == nullptr) {
397 return;
398 }
399 ffStrbufPrependNS(strbuf, value->length, value->chars);
400}
401
402[[gnu::nonnull(1)]] static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) {
403 if (value == nullptr) {
404 return;
405 }
406 ffStrbufPrependNS(strbuf, (uint32_t) strlen(value), value);
407}
408
409[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
410 uint32_t length = strbuf->length > comp->length ? comp->length : strbuf->length;
411 return memcmp(strbuf->chars, comp->chars, length + 1);
412}
413
414[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
415 return ffStrbufComp(strbuf, comp) == 0;
416}
417
418[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) {
419 return strcmp(strbuf->chars, comp);
420}
421
422[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) {
423 return ffStrbufCompS(strbuf, comp) == 0;
424}
425
426[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) {
427 return strcasecmp(strbuf->chars, comp);
428}
429
430[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) {
431 return ffStrbufIgnCaseCompS(strbuf, comp) == 0;
432}
433
434[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
435 return ffStrbufIgnCaseCompS(strbuf, comp->chars);
436}
437
438[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
439 return ffStrbufIgnCaseComp(strbuf, comp) == 0;
440}
441
442[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufContainC(const FFstrbuf* strbuf, char c) {
443 return memchr(strbuf->chars, c, strbuf->length) != nullptr;
444}
445
446[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) {
447 return strstr(strbuf->chars, str) != nullptr;
448}
449
450[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) {
451 return ffStrbufContainS(strbuf, str->chars);
452}
453
454[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) {
455 return strcasestr(strbuf->chars, str) != nullptr;
456}
457
458[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) {
459 return ffStrbufContainIgnCaseS(strbuf, str->chars);
460}
461
462[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
463 assert(start <= strbuf->length);
464
465 const char* ptr = (const char*) memchr(strbuf->chars + start, c, strbuf->length - start);
466 return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
467}
468
469[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufContainAnyC(const FFstrbuf* strbuf, const char* cs) {
470 assert(cs != nullptr);
471
472 return strpbrk(strbuf->chars, cs) != nullptr;
473}
474
475[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) {
476 assert(start <= strbuf->length);
477
478 const char* ptr = strstr(strbuf->chars + start, str);
479 return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
480}
481
482[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
483 assert(start <= strbuf->length);
484
485 const char* ptr = (const char*) memrchr(strbuf->chars, c, start + 1);
486 return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
487}
488
489[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) {
490 return ffStrbufNextIndexC(strbuf, 0, c);
491}
492
493[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) {
494 return ffStrbufNextIndexS(strbuf, 0, searched->chars);
495}
496
497[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) {
498 return ffStrbufNextIndexS(strbuf, 0, str);
499}
500
501[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) {
502 if (strbuf->length == 0) {
503 return 0;
504 }
505
506 return ffStrbufPreviousIndexC(strbuf, strbuf->length - 1, c);
507}
508
509[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) {
510 return ffStrbufSubstrBefore(strbuf, ffStrbufFirstIndexC(strbuf, c));
511}
512
513[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) {
514 return ffStrbufSubstrBefore(strbuf, ffStrbufLastIndexC(strbuf, c));
515}
516
517[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) {
518 return strbuf->chars[0] == c;
519}
520
521[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) {
522 if (length > strbuf->length) {
523 return false;
524 }
525
526 return memcmp(strbuf->chars, start, length) == 0;
527}
528
529[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) {
530 return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start));
531}
532
533[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) {
534 return ffStrbufStartsWithSN(strbuf, start->chars, start->length);
535}
536
537[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) {
538 if (length > strbuf->length) {
539 return false;
540 }
541 return strncasecmp(strbuf->chars, start, length) == 0;
542}
543
544[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) {
545 return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start);
546}
547
548[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) {
549 return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars);
550}
551
552[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) {
553 return strbuf->length == 0 ? false : strbuf->chars[strbuf->length - 1] == c;
554}
555
556[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
557 if (endLength > strbuf->length) {
558 return false;
559 }
560
561 return memcmp(strbuf->chars + strbuf->length - endLength, end, endLength) == 0;
562}
563
564[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) {
565 return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end);
566}
567
568// Not `pure`: the caller-supplied `fn` may have side effects
569[[gnu::nonnull(1, 2), nodiscard]] static inline bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) {
570 return strbuf->length == 0 ? false : fn(strbuf->chars[strbuf->length - 1]);
571}
572
573[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) {
574 return ffStrbufEndsWithNS(strbuf, end->length, end->chars);
575}
576
577[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
578 if (endLength > strbuf->length) {
579 return false;
580 }
581 return strcasecmp(strbuf->chars + strbuf->length - endLength, end) == 0;
582}
583
584[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) {
585 return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end);
586}
587
588[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) {
589 return ffStrbufEndsWithIgnCaseNS(strbuf, end->length, end->chars);
590}
591
592[[gnu::nonnull(1)]] static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) {
593 ffStrbufTrimRight(strbuf, c);
594 ffStrbufTrimLeft(strbuf, c);
595}
596
597[[gnu::nonnull(1)]] static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) {
598 ffStrbufTrimRightSpace(strbuf);
599 ffStrbufTrimLeftSpace(strbuf);
600}
601
602[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) {
603 return ffStrbufMatchSeparatedNS(strbuf, (uint32_t) strlen(comp), comp, separator);
604}
605
606[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
607 return ffStrbufMatchSeparatedNS(strbuf, comp->length, comp->chars, separator);
608}
609
610[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
611 return ffStrbufMatchSeparatedIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);
612}
613
614[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
615 return ffStrbufMatchSeparatedIgnCaseNS(strbuf, comp->length, comp->chars, separator);
616}
617
618[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) {
619 return ffStrbufSeparatedContainNS(strbuf, (uint32_t) strlen(comp), comp, separator);
620}
621
622[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
623 return ffStrbufSeparatedContainNS(strbuf, comp->length, comp->chars, separator);
624}
625
626[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
627 return ffStrbufSeparatedContainIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);
628}
629
630[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
631 return ffStrbufSeparatedContainIgnCaseNS(strbuf, comp->length, comp->chars, separator);
632}
633
634[[gnu::nonnull(1, 2)]] static inline void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) {
635 fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file);
636}
637
638[[gnu::nonnull(1, 2)]] static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) {
639 ffStrbufWriteTo(strbuf, file);
640 fputc('\n', file);
641}
642
643// Not `pure`: `strtod` reads the LC_NUMERIC locale and writes errno
644[[gnu::nonnull(1), nodiscard]] static inline double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) {
645 char* str_end;
646 double result = strtod(strbuf->chars, &str_end);
647 return str_end == strbuf->chars ? defaultValue : result;
648}
649
650// Not `pure`: `strtoull` reads the LC_NUMERIC locale and writes errno
651[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) {
652 char* str_end;
653 unsigned long long result = strtoull(strbuf->chars, &str_end, 10);
654 return str_end == strbuf->chars ? defaultValue : (uint64_t) result;
655}
656
657// Not `pure`: `strtoll` reads the LC_NUMERIC locale and writes errno
658[[gnu::nonnull(1), nodiscard]] static inline int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) {
659 char* str_end;
660 long long result = strtoll(strbuf->chars, &str_end, 10);
661 return str_end == strbuf->chars ? defaultValue : (int64_t) result;
662}
663
664// Returns true if the strbuf is modified
665[[gnu::nonnull(1)]] [[gnu::nonnull(1)]] bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf);
666
667#define FF_STRBUF_AUTO_DESTROY [[gnu::cleanup(ffStrbufDestroy)]] FFstrbuf
668#define FF_STRBUF_STATIC(str) { .allocated = 0, .length = (uint32_t) sizeof(str) - 1, .chars = str }