45#define CSTR_MAX_SIZE ((size_t)CSTR_MAX_LEN)
48#if defined(__GNUC__) || defined(__clang__)
49#define likely(x) __builtin_expect(!!(x), 1)
50#define unlikely(x) __builtin_expect(!!(x), 0)
53#define unlikely(x) (x)
57static inline uint32_t next_pow2_u32(uint32_t x) {
69static inline uint32_t cstr_grow_cap(uint32_t current, uint32_t need) {
70 uint32_t cap = current < CSTR_MIN_HEAP ? CSTR_MIN_HEAP : current;
88static bool cstr_ensure_cap(
cstr* s,
size_t need) {
90 if (CSTR_UNLIKELY(need > (
size_t)
CSTR_MAX_LEN))
return false;
92 uint32_t need32 = (uint32_t)need;
99 char* mem = (
char*)malloc(cap);
100 if (CSTR_UNLIKELY(!mem))
return false;
110 if (need32 <= cur_cap)
return true;
112 uint32_t new_cap = cstr_grow_cap(cur_cap, need32);
113 if (new_cap == 0)
return false;
115 char* mem = (
char*)realloc(s->
data, new_cap);
116 if (CSTR_UNLIKELY(!mem))
return false;
128 if (initial_capacity >= CSTR_MAX_SIZE) {
133 if (CSTR_UNLIKELY(!s))
return NULL;
138 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, initial_capacity + 1))) {
147 if (CSTR_UNLIKELY(!input))
return NULL;
152 if (CSTR_UNLIKELY(!data && length > 0))
return NULL;
155 if (CSTR_UNLIKELY(!s))
return NULL;
159 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, length + 1))) {
163 memcpy(s->
data, data, length);
164 s->
data[length] =
'\0';
165 s->
length = (uint32_t)length;
190 fprintf(stderr,
"cstr: NULL\n");
195 "cstr { data=%p, length=%u, capacity=%u, mode=%s }\n"
196 " content: \"%.*s\"\n",
209 uint32_t needed = s->
length + 1;
212 char* mem = (
char*)realloc(s->
data, needed);
225 size_t n = strlen(append_str);
226 if (n == 0)
return true;
228 size_t new_len = (size_t)s->
length + n;
229 if (CSTR_UNLIKELY(new_len > CSTR_MAX_SIZE))
return false;
230 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, new_len + 1)))
return false;
232 memcpy(s->
data + s->
length, append_str, n + 1);
233 s->
length = (uint32_t)new_len;
238 uint32_t n = append->
length;
239 if (n == 0)
return true;
241 uint32_t new_len = s->
length + n;
242 if (CSTR_UNLIKELY(new_len < s->length))
return false;
243 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, (
size_t)new_len + 1)))
return false;
251 uint32_t copy_n = (n < (size_t)src->
length) ? (uint32_t)n : src->length;
252 if (copy_n == 0)
return true;
254 uint32_t new_len = dest->
length + copy_n;
255 if (CSTR_UNLIKELY(new_len < dest->length))
return false;
256 if (CSTR_UNLIKELY(!cstr_ensure_cap(dest, (
size_t)new_len + 1)))
return false;
259 dest->
data[new_len] =
'\0';
265 uint32_t new_len = s->
length + 1;
266 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, (
size_t)new_len + 1)))
return false;
268 s->
data[new_len] =
'\0';
274 size_t n = strlen(prepend_str);
275 if (n == 0)
return true;
277 size_t new_len = (size_t)s->
length + n;
278 if (CSTR_UNLIKELY(new_len > CSTR_MAX_SIZE))
return false;
279 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, new_len + 1)))
return false;
282 memcpy(s->
data, prepend_str, n);
283 s->
length = (uint32_t)new_len;
288 uint32_t n = prepend->
length;
289 if (n == 0)
return true;
291 uint32_t new_len = s->
length + n;
292 if (CSTR_UNLIKELY(new_len < s->length))
return false;
293 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, (
size_t)new_len + 1)))
return false;
302 size_t n = strlen(prepend_str);
303 if (n == 0)
return true;
305 memcpy(s->
data, prepend_str, n);
311 if (CSTR_UNLIKELY(index > (
size_t)s->
length))
return false;
313 size_t n = strlen(insert_str);
314 if (n == 0)
return true;
316 size_t new_len = (size_t)s->
length + n;
317 if (CSTR_UNLIKELY(new_len > CSTR_MAX_SIZE))
return false;
318 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, new_len + 1)))
return false;
320 char* pos = s->
data + index;
321 memmove(pos + n, pos, s->
length - index + 1);
322 memcpy(pos, insert_str, n);
323 s->
length = (uint32_t)new_len;
328 if (CSTR_UNLIKELY(index > (
size_t)s->
length))
return false;
330 uint32_t n = insert->
length;
331 if (n == 0)
return true;
333 uint32_t new_len = s->
length + n;
334 if (CSTR_UNLIKELY(new_len < s->length))
return false;
335 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, (
size_t)new_len + 1)))
return false;
337 char* pos = s->
data + index;
338 memmove(pos + n, pos, s->
length - index + 1);
339 memcpy(pos, insert->
data, n);
346 if (CSTR_UNLIKELY(index > len))
return (index == len && count == 0);
348 uint32_t idx = (uint32_t)index;
349 uint32_t cnt = (count > (size_t)(len - idx)) ? (len - idx) : (uint32_t)count;
350 if (cnt == 0)
return true;
352 memmove(s->
data + idx, s->
data + idx + cnt, len - idx - cnt + 1);
361cstr* cstr_format(
const char* format, ...) {
362 if (CSTR_UNLIKELY(!format))
return NULL;
367 int need = vsnprintf(NULL, 0, format, a2);
370 if (CSTR_UNLIKELY(need < 0 || (
size_t)need > CSTR_MAX_SIZE)) {
376 if (CSTR_UNLIKELY(!s)) {
381 vsnprintf(s->
data, (
size_t)need + 1, format, a);
383 s->
length = (uint32_t)need;
387bool cstr_append_fmt(
cstr* s,
const char* format, ...) {
391 int n = vsnprintf(NULL, 0, format, a2);
394 if (CSTR_UNLIKELY(n < 0)) {
399 uint32_t new_len = s->
length + (uint32_t)n;
400 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, (
size_t)new_len + 1))) {
405 vsnprintf(s->
data + s->
length, (
size_t)n + 1, format, a);
416 if (dest == src)
return true;
417 uint32_t src_len = src->
length;
418 if (CSTR_UNLIKELY(!cstr_ensure_cap(dest, (
size_t)src_len + 1)))
return false;
419 memcpy(dest->
data, src->
data, (
size_t)src_len + 1);
429 if (!*substr)
return 0;
430 size_t sub_len = strlen(substr);
433 const char* end = d + s->
length;
437 size_t rem = (size_t)(end - r);
438 if (rem >= sub_len && memcmp(r, substr, sub_len) == 0) {
446 s->
length = (uint32_t)(w - d);
450size_t cstr_remove_all_cstr(
cstr* s,
const cstr* substr) {
451 uint32_t sub_len = substr->
length;
452 if (sub_len == 0)
return 0;
453 const char* sub = substr->
data;
456 const char* end = d + s->
length;
460 size_t rem = (size_t)(end - r);
461 if ((uint32_t)rem >= sub_len && memcmp(r, sub, sub_len) == 0) {
469 s->
length = (uint32_t)(w - d);
476 const char* end = d + s->
length;
478 if (*d != c) *w++ = *d;
487 if (CSTR_UNLIKELY(start >= len || slen == 0))
return;
488 if (slen > len - start) slen = len - start;
490 size_t tail = len - start - slen;
492 memmove(d + start, d + start + slen, tail + 1);
495 s->
length = len - (uint32_t)slen;
513static const char* cstr_search(
const char* hs,
size_t hlen,
const char* nd,
size_t nlen) {
515 if (unlikely(nlen == 0))
return hs;
516 if (unlikely(hlen < nlen))
return NULL;
519 if (nlen == 1)
return (
const char*)memchr(hs, (
unsigned char)nd[0], hlen);
521 const char* cur = hs;
522 const char* end = hs + hlen - nlen;
523 unsigned char n_first = (
unsigned char)nd[0];
524 unsigned char n_last = (
unsigned char)nd[nlen - 1];
530 cur = (
const char*)memchr(cur, n_first, (
size_t)(end - cur + 1));
531 if (unlikely(!cur))
return NULL;
534 if ((
unsigned char)cur[nlen - 1] == n_last) {
540 const char* p_hay = cur + 1;
541 const char* p_nd = nd + 1;
549 if (p_hay[i] != p_nd[i])
goto next_iter;
555 if (memcmp(cur + 1, nd + 1, nlen - 2) == 0)
return cur;
571 size_t nlen = strlen(substr);
572 const char* found = cstr_search(s->
data, s->
length, substr, nlen);
576int cstr_find_cstr(
const cstr* s,
const cstr* sub) {
582 size_t nlen = strlen(substr);
585 const char* hs = s->
data;
587 const char* last = NULL;
591 while ((p = cstr_search(p, hlen - (
size_t)(p - hs), substr, nlen)) != NULL) {
594 if ((
size_t)(p - hs) + nlen > hlen)
break;
596 return last ? (int)(last - hs) :
CSTR_NPOS;
599int cstr_rfind_cstr(
const cstr* s,
const cstr* sub) {
603 const char* hs = s->
data;
605 const char* last = NULL;
608 while ((p = cstr_search(p, hlen - (
size_t)(p - hs), sub->
data, sub->
length)) != NULL) {
611 if ((
size_t)(p - hs) + sub->
length > hlen)
break;
613 return last ? (int)(last - hs) :
CSTR_NPOS;
621 if (!s1 && !s2)
return 0;
627int cstr_ncmp(
const cstr* s1,
const cstr* s2,
size_t n) {
628 if (!s1 && !s2)
return 0;
631 return strncmp(s1->
data, s2->
data, n);
638bool cstr_starts_with(
const cstr* s,
const char* prefix) {
639 size_t plen = strlen(prefix);
640 if (plen == 0)
return true;
641 if (plen > (
size_t)s->
length)
return false;
642 return memcmp(s->
data, prefix, plen) == 0;
645bool cstr_starts_with_cstr(
const cstr* s,
const cstr* prefix) {
646 uint32_t plen = prefix->
length;
647 if (plen == 0)
return true;
648 if (plen > s->
length)
return false;
649 return memcmp(s->
data, prefix->
data, plen) == 0;
652bool cstr_ends_with(
const cstr* s,
const char* suffix) {
653 size_t slen = strlen(suffix);
654 if (slen == 0)
return true;
655 if (slen > (
size_t)s->
length)
return false;
656 return memcmp(s->
data + s->
length - slen, suffix, slen) == 0;
659bool cstr_ends_with_cstr(
const cstr* s,
const cstr* suffix) {
660 uint32_t slen = suffix->
length;
661 if (slen == 0)
return true;
662 if (slen > s->
length)
return false;
663 return memcmp(s->
data + s->
length - slen, suffix->
data, slen) == 0;
671 size_t nlen = strlen(substr);
672 if (nlen == 0 || nlen > s->
length)
return 0;
675 const char* p = s->
data;
678 while ((p = cstr_search(p, rem, substr, nlen)) != NULL) {
682 if (rem < nlen)
break;
687size_t cstr_count_substr_cstr(
const cstr* s,
const cstr* sub) {
689 size_t nlen = sub->
length;
692 const char* p = s->
data;
695 while ((p = cstr_search(p, rem, sub->
data, nlen)) != NULL) {
699 if (rem < nlen)
break;
708void cstr_lower(
cstr* s) {
710 for (uint32_t i = 0, n = s->
length; i < n; i++) {
711 unsigned char c = (
unsigned char)d[i];
713 if ((
unsigned)(c -
'A') <= 25u) d[i] = (char)(c | 0x20u);
717void cstr_upper(
cstr* s) {
719 for (uint32_t i = 0, n = s->
length; i < n; i++) {
720 unsigned char c = (
unsigned char)d[i];
722 if ((
unsigned)(c -
'a') <= 25u) d[i] = (char)(c & ~0x20u);
726bool cstr_snakecase(
cstr* s) {
727 uint32_t orig = s->
length;
728 if (orig == 0)
return true;
731 const char* d = s->
data;
733 for (uint32_t i = 1; i < orig; i++) {
734 if ((
unsigned)((
unsigned char)d[i] -
'A') <= 25u) extra++;
741 uint32_t new_len = orig + extra;
742 if (CSTR_UNLIKELY(!cstr_ensure_cap(s, new_len + 1)))
return false;
746 char* w = s->
data + new_len;
749 for (uint32_t i = orig; i > 0;) {
751 unsigned char c = (
unsigned char)d[i];
752 if (i > 0 && (
unsigned)(c -
'A') <= 25u) {
753 *w-- = (char)(c | 0x20u);
756 *w-- = (char)((
unsigned)(c -
'A') <= 25u ? (c | 0x20u) : c);
763void cstr_camelcase(
cstr* s) {
765 if (len == 0)
return;
767 uint32_t r = 0, w = 0;
770 while (r < len && (d[r] ==
'_' || isspace((
unsigned char)d[r]))) r++;
772 unsigned char c = (
unsigned char)d[r++];
773 d[w++] = (char)((
unsigned)(c -
'A') <= 25u ? (c | 0x20u) : c);
778 unsigned char c = (
unsigned char)d[r++];
779 if (c ==
'_' || isspace(c)) {
784 d[w++] = (char)toupper(c);
787 d[w++] = (char)tolower(c);
794void cstr_pascalcase(
cstr* s) {
796 if (len == 0)
return;
798 uint32_t r = 0, w = 0;
800 while (r < len && (d[r] ==
'_' || isspace((
unsigned char)d[r]))) r++;
802 bool new_word =
true;
804 unsigned char c = (
unsigned char)d[r++];
805 if (c ==
'_' || isspace(c)) {
809 d[w++] = new_word ? (char)toupper(c) : (char)tolower(c);
816void cstr_titlecase(
cstr* s) {
820 for (uint32_t i = 0; i < len; i++) {
821 unsigned char c = (
unsigned char)d[i];
825 d[i] = (char)toupper(c);
828 d[i] = (char)tolower(c);
837void cstr_trim(
cstr* s) {
839 if (len == 0)
return;
842 uint32_t start = 0, end = len - 1;
843 while (start < len && isspace((
unsigned char)d[start])) start++;
844 while (end > start && isspace((
unsigned char)d[end])) end--;
846 uint32_t new_len = (start > end) ? 0 : (end - start + 1);
847 if (new_len && start) memmove(d, d + start, new_len);
852void cstr_rtrim(
cstr* s) {
854 if (len == 0)
return;
857 while (e > 0 && isspace((
unsigned char)d[e - 1])) e--;
862void cstr_ltrim(
cstr* s) {
864 if (len == 0)
return;
867 while (start < len && isspace((
unsigned char)d[start])) start++;
868 if (start == 0)
return;
869 uint32_t new_len = len - start;
870 memmove(d, d + start, new_len + 1);
874void cstr_trim_chars(
cstr* s,
const char* chars) {
876 if (len == 0 || *chars ==
'\0')
return;
880 while (start < len && strchr(chars, d[start])) start++;
887 uint32_t end = len - 1;
888 while (end > start && strchr(chars, d[end])) end--;
890 uint32_t new_len = end - start + 1;
891 if (start) memmove(d, d + start, new_len);
900cstr* cstr_substr(
const cstr* s,
size_t start,
size_t length) {
901 uint32_t slen = s->
length;
902 if (CSTR_UNLIKELY(start > slen))
return NULL;
903 uint32_t avail = slen - (uint32_t)start;
904 uint32_t copy = (length > avail) ? avail : (uint32_t)length;
912cstr* cstr_replace(
const cstr* s,
const char* old_str,
const char* new_str) {
913 size_t old_len = strlen(old_str);
916 const char* found = cstr_search(s->
data, s->
length, old_str, old_len);
919 size_t new_len = strlen(new_str);
920 size_t prefix_len = (size_t)(found - s->
data);
921 size_t suffix_len = s->
length - prefix_len - old_len;
922 size_t result_len = prefix_len + new_len + suffix_len;
925 if (CSTR_UNLIKELY(!r))
return NULL;
928 memcpy(d, s->
data, prefix_len);
929 memcpy(d + prefix_len, new_str, new_len);
930 memcpy(d + prefix_len + new_len, found + old_len, suffix_len);
931 d[result_len] =
'\0';
932 r->
length = (uint32_t)result_len;
940#define RA_STACK_CAP 64
942cstr* cstr_replace_all(
const cstr* s,
const char* old_sub,
const char* new_sub) {
943 size_t old_len = strlen(old_sub);
946 size_t new_len = strlen(new_sub);
947 const char* hs = s->
data;
951 size_t stack_offs[RA_STACK_CAP];
952 size_t* offs = stack_offs;
953 size_t offs_cap = RA_STACK_CAP;
959 while ((p = cstr_search(p, rem, old_sub, old_len)) != NULL) {
960 if (CSTR_UNLIKELY(count >= offs_cap)) {
962 if (CSTR_UNLIKELY(offs_cap > SIZE_MAX / 2 /
sizeof(
size_t)))
goto oom;
964 size_t new_cap = offs_cap * 2;
966 if (offs == stack_offs) {
967 no = (
size_t*)malloc(new_cap *
sizeof(
size_t));
968 if (CSTR_UNLIKELY(!no))
goto oom;
969 memcpy(no, stack_offs, count *
sizeof(
size_t));
971 no = (
size_t*)realloc(offs, new_cap *
sizeof(
size_t));
972 if (CSTR_UNLIKELY(!no))
goto oom;
977 offs[count++] = (size_t)(p - hs);
979 rem = hlen - (size_t)(p - hs);
983 if (offs != stack_offs) free(offs);
989 if (new_len >= old_len)
990 result_len = hlen + count * (new_len - old_len);
992 result_len = hlen - count * (old_len - new_len);
996 if (CSTR_UNLIKELY(!r))
goto oom;
999 size_t write_pos = 0;
1002 for (
size_t i = 0; i < count; i++) {
1003 size_t gap = offs[i] - src_pos;
1005 memcpy(dst + write_pos, hs + src_pos, gap);
1009 memcpy(dst + write_pos, new_sub, new_len);
1010 write_pos += new_len;
1012 src_pos = offs[i] + old_len;
1014 size_t tail = hlen - src_pos;
1016 memcpy(dst + write_pos, hs + src_pos, tail);
1020 dst[write_pos] =
'\0';
1021 r->
length = (uint32_t)write_pos;
1023 if (offs != stack_offs) free(offs);
1028 if (offs != stack_offs) free(offs);
1040 if (!delim || !*delim) {
1042 if (!r)
return NULL;
1052 size_t dlen = strlen(delim);
1055 if (!result)
return NULL;
1057 const char* start = s->
data;
1062 const char* found = cstr_search(start, (
size_t)(end - start), delim, dlen);
1063 const char* tok_end = found ? found : end;
1065 if (CSTR_UNLIKELY(count >= cap)) {
1067 cstr** tmp = (
cstr**)realloc(result, cap *
sizeof(
cstr*));
1068 if (!tmp)
goto split_err;
1072 result[count] =
cstr_new_len(start, (
size_t)(tok_end - start));
1073 if (!result[count])
goto split_err;
1077 start = found + dlen;
1084 for (
size_t i = 0; i < count; i++)
cstr_free(result[i]);
1090 if (!strings || count == 0)
return cstr_new_len(
"", 0);
1092 size_t dlen = delim ? strlen(delim) : 0;
1094 for (
size_t i = 0; i < count; i++) {
1095 if (CSTR_UNLIKELY(!strings[i]))
return NULL;
1096 total += strings[i]->
length;
1097 if (i + 1 < count) total += dlen;
1101 if (!r)
return NULL;
1105 for (
size_t i = 0; i < count; i++) {
1106 uint32_t len = strings[i]->
length;
1108 memcpy(d + pos, strings[i]->data, len);
1111 if (dlen && i + 1 < count) {
1112 memcpy(d + pos, delim, dlen);
1117 r->
length = (uint32_t)pos;
1126 uint32_t len = s->
length;
1128 if (!r)
return NULL;
1129 char* dst = r->
data;
1130 const char* src = s->
data;
1131 for (uint32_t i = 0; i < len; i++) dst[i] = src[len - 1 - i];
1137void cstr_reverse_inplace(
cstr* s) {
1138 uint32_t len = s->
length;
1139 if (len < 2)
return;
1141 for (uint32_t i = 0, j = len - 1; i < j; i++, j--) {
void cstr_remove_char(cstr *s, char c)
Remove a specific character from every position.
cstr * cstr_join(const cstr **strings, size_t count, const char *delim)
Join an array of cstr pointers with a delimiter.
void cstr_debug(const cstr *s)
Print debug information about a cstr to stderr.
bool cstr_remove(cstr *s, size_t index, size_t count)
Remove count characters starting at index.
cstr * cstr_new(const char *input)
Create a new cstr from a C string.
cstr * cstr_new_len(const char *data, size_t length)
Create a new cstr from a buffer of known length (no strlen needed).
bool cstr_append_char(cstr *s, char c)
Append a single character.
bool cstr_reserve(cstr *s, size_t capacity)
Ensure at least capacity usable bytes are available (NUL extra).
void cstr_shrink_to_fit(cstr *s)
Shrink heap allocation to fit the current length (frees wasted memory).
void cstr_remove_substr(cstr *s, size_t start, size_t slen)
Remove a run of substr_length bytes starting at start.
void cstr_free(cstr *s)
Free a heap-allocated cstr and its storage. Safe to call with NULL.
int cstr_cmp(const cstr *s1, const cstr *s2)
Lexicographic compare. NULL < non-NULL; two NULLs are equal.
void cstr_drop(cstr *s)
Release only the internal heap buffer of an embedded cstr (one created via cstr_init_inplace)....
cstr * cstr_init(size_t initial_capacity)
Create a new heap-allocated cstr with a given initial capacity.
High-performance C string with Small String Optimization (SSO).
bool bool cstr_append_cstr(cstr *s, const cstr *append) CSTR_NONNULL(1
Append another cstr.
CSTR_INLINE bool cstr_is_heap(const cstr *s) CSTR_PURE
CSTR_INLINE uint32_t cstr_heap_cap(const cstr *s) CSTR_PURE
size_t cstr_count_substr(const cstr *s, const char *substr) CSTR_NONNULL(1
size_t cstr_remove_all(cstr *s, const char *substr) CSTR_NONNULL(1
Remove all occurrences of substr (in-place, single pass).
bool cstr_prepend(cstr *s, const char *prepend) CSTR_NONNULL(1
Prepend a NUL-terminated C string.
bool bool bool bool cstr_insert(cstr *s, size_t index, const char *insert) CSTR_NONNULL(1
Insert a C string at byte offset index.
bool cstr_append(cstr *s, const char *CSTR_RESTRICT append) CSTR_NONNULL(1
Append a NUL-terminated C string.
bool bool bool bool bool cstr_insert_cstr(cstr *s, size_t index, const cstr *insert) CSTR_NONNULL(1
Insert a cstr at byte offset index.
int cstr_rfind(const cstr *s, const char *substr) CSTR_NONNULL(1
CSTR_INLINE void cstr_init_inplace(cstr *s)
Initialize an already-allocated cstr in SSO mode (no heap).
bool bool bool cstr_prepend_fast(cstr *s, const char *prepend) CSTR_NONNULL(1
Prepend without capacity check — caller guarantees space.
bool bool cstr_prepend_cstr(cstr *s, const cstr *prepend) CSTR_NONNULL(1
Prepend another cstr.
cstr ** cstr_split(const cstr *s, const char *delim, size_t *count_out) CSTR_NONNULL(1
Split on delimiter. Returns array of cstr* (each must be freed), terminated by setting *count_out....
int cstr_find(const cstr *s, const char *substr) CSTR_NONNULL(1
Find first occurrence of substr. Uses optimised search (no memmem).
bool bool bool cstr_ncat(cstr *dest, const cstr *src, size_t n) CSTR_NONNULL(1
Append at most n chars from src.
bool cstr_copy(cstr *dest, const cstr *src) CSTR_NONNULL(1
Deep copy src into dest (dest is overwritten).
A dynamically resizable C string with SSO.