71static inline const char* str_search_impl(
const char* hs,
size_t hlen,
const char* nd,
size_t nlen) {
72 if (nlen == 0)
return hs;
73 if (hlen < nlen)
return NULL;
74 if (nlen == 1)
return (
const char*)memchr(hs, (
unsigned char)nd[0], hlen);
77 const char* end = hs + hlen - nlen;
78 unsigned char first = (
unsigned char)nd[0];
79 unsigned char last = (
unsigned char)nd[nlen - 1];
82 cur = (
const char*)memchr(cur, first, (
size_t)(end - cur + 1));
83 if (!cur)
return NULL;
85 if ((
unsigned char)cur[nlen - 1] == last) {
89 for (; i < nlen - 2; i++) {
90 if (cur[1 + i] != nd[1 + i])
goto next_iter;
94 if (memcmp(cur + 1, nd + 1, nlen - 2) == 0)
return cur;
116static inline bool str_is_empty(
const char* str) {
return !str || str[0] ==
'\0'; }
125static inline bool str_is_blank(
const char* str) {
126 if (!str)
return true;
127 for (; *str; str++) {
128 if (!isspace((
unsigned char)*str))
return false;
141static inline bool str_is_alpha(
const char* str) {
142 if (!str || !*str)
return false;
143 for (; *str; str++) {
144 if (!isalpha((
unsigned char)*str))
return false;
157static inline bool str_is_digit(
const char* str) {
158 if (!str || !*str)
return false;
159 for (; *str; str++) {
160 if (!isdigit((
unsigned char)*str))
return false;
173static inline bool str_is_alnum(
const char* str) {
174 if (!str || !*str)
return false;
175 for (; *str; str++) {
176 if (!isalnum((
unsigned char)*str))
return false;
189static inline bool str_is_numeric(
const char* str) {
190 if (!str || !*str)
return false;
191 if (*str ==
'+' || *str ==
'-') str++;
192 if (!*str)
return false;
193 for (; *str; str++) {
194 if (!isdigit((
unsigned char)*str))
return false;
207static inline bool str_is_float(
const char* str) {
208 if (!str || !*str)
return false;
211 (void)strtod(str, &end);
212 return *end ==
'\0' && end != str;
222static inline bool str_equals(
const char* a,
const char* b) {
223 if (a == b)
return true;
224 if (!a || !b)
return false;
225 return strcmp(a, b) == 0;
235static inline bool str_iequals(
const char* a,
const char* b) {
236 if (a == b)
return true;
237 if (!a || !b)
return false;
238 for (; *a && *b; a++, b++) {
239 if (tolower((
unsigned char)*a) != tolower((
unsigned char)*b))
return false;
241 return *a ==
'\0' && *b ==
'\0';
253static inline bool str_contains(
const char* str,
const char* substr) {
254 if (!str || !substr)
return false;
255 size_t hlen = strlen(str);
256 size_t nlen = strlen(substr);
257 return str_search_impl(str, hlen, substr, nlen) != NULL;
268static inline bool str_starts_with(
const char* str,
const char* prefix) {
269 if (!str || !prefix)
return false;
270 size_t plen = strlen(prefix);
271 if (plen == 0)
return true;
272 return strncmp(str, prefix, plen) == 0;
283static inline bool str_ends_with(
const char* str,
const char* suffix) {
284 if (!str || !suffix)
return false;
285 size_t slen = strlen(suffix);
286 if (slen == 0)
return true;
287 size_t len = strlen(str);
288 if (slen > len)
return false;
289 return memcmp(str + len - slen, suffix, slen) == 0;
304static inline int str_find(
const char* str,
const char* substr) {
305 if (!str || !substr)
return -1;
306 size_t hlen = strlen(str);
307 size_t nlen = strlen(substr);
308 const char* found = str_search_impl(str, hlen, substr, nlen);
309 return found ? (int)(found - str) : -1;
323static inline int str_rfind(
const char* str,
const char* substr) {
324 if (!str || !substr)
return -1;
325 size_t hlen = strlen(str);
326 size_t nlen = strlen(substr);
327 if (nlen == 0 || nlen > hlen)
return -1;
329 const char* last = NULL;
332 while ((p = str_search_impl(p, hlen - (
size_t)(p - str), substr, nlen)) != NULL) {
335 if ((
size_t)(p - str) + nlen > hlen)
break;
337 return last ? (int)(last - str) : -1;
351static inline size_t str_count_substr(
const char* str,
const char* substr) {
352 if (!str || !substr)
return 0;
353 size_t nlen = strlen(substr);
354 size_t hlen = strlen(str);
355 if (nlen == 0 || nlen > hlen)
return 0;
361 while ((p = str_search_impl(p, rem, substr, nlen)) != NULL) {
364 rem = hlen - (size_t)(p - str);
365 if (rem < nlen)
break;
380static inline size_t str_word_count(
const char* str) {
383 bool in_word =
false;
384 for (; *str; str++) {
385 if (isspace((
unsigned char)*str)) {
387 }
else if (!in_word) {
407static inline void str_lower(
char* str) {
409 for (; *str; str++) {
410 unsigned char c = (
unsigned char)*str;
411 if ((
unsigned)(c -
'A') <= 25u) *str = (char)(c | 0x20u);
423static inline void str_upper(
char* str) {
425 for (; *str; str++) {
426 unsigned char c = (
unsigned char)*str;
427 if ((
unsigned)(c -
'a') <= 25u) *str = (char)(c & ~0x20u);
439static inline void str_capitalize(
char* str) {
440 if (!str || !*str)
return;
441 *str = (char)toupper((
unsigned char)*str);
443 for (; *str; str++) *str = (
char)tolower((
unsigned char)*str);
456static inline void str_camelcase(
char* str) {
457 if (!str || !*str)
return;
461 size_t len = strlen(str);
464 while (r < len && (str[r] ==
'_' || isspace((
unsigned char)str[r]))) r++;
468 unsigned char c = (
unsigned char)str[r++];
469 str[w++] = (char)((
unsigned)(c -
'A') <= 25u ? (c | 0x20u) : c);
474 unsigned char c = (
unsigned char)str[r++];
475 if (c ==
'_' || isspace(c)) {
478 str[w++] = (char)toupper(c);
481 str[w++] = (char)tolower(c);
495static inline void str_pascalcase(
char* str) {
496 if (!str || !*str)
return;
500 size_t len = strlen(str);
503 while (r < len && (str[r] ==
'_' || isspace((
unsigned char)str[r]))) r++;
505 bool new_word =
true;
507 unsigned char c = (
unsigned char)str[r++];
508 if (c ==
'_' || isspace(c)) {
511 str[w++] = new_word ? (char)toupper(c) : (char)tolower(c);
528static inline void str_titlecase(
char* str) {
531 for (; *str; str++) {
532 unsigned char c = (
unsigned char)*str;
536 *str = (char)toupper(c);
539 *str = (char)tolower(c);
556static inline void str_ltrim(
char* str) {
557 if (!str || !*str)
return;
558 size_t len = strlen(str);
560 while (start < len && isspace((
unsigned char)str[start])) start++;
561 if (start == 0)
return;
562 memmove(str, str + start, len - start + 1);
571static inline void str_rtrim(
char* str) {
572 if (!str || !*str)
return;
573 size_t len = strlen(str);
574 while (len > 0 && isspace((
unsigned char)str[len - 1])) len--;
586static inline void str_trim(
char* str) {
587 if (!str || !*str)
return;
601static inline void str_trim_chars(
char* str,
const char* chars) {
602 if (!str || !chars || !*chars)
return;
603 size_t len = strlen(str);
606 while (start < len && strchr(chars, str[start])) start++;
612 size_t end = len - 1;
613 while (end > start && strchr(chars, str[end])) end--;
615 size_t new_len = end - start + 1;
616 if (start) memmove(str, str + start, new_len);
629static inline void str_truncate(
char* str,
size_t max_len) {
631 size_t len = strlen(str);
632 if (len > max_len) str[max_len] =
'\0';
640static inline void str_reverse(
char* str) {
642 size_t len = strlen(str);
644 for (
size_t i = 0, j = len - 1; i < j; i++, j--) {
658static inline void str_remove_char(
char* str,
char c) {
660 char *w = str, *r = str;
662 if (*r != c) *w++ = *r;
676static inline size_t str_remove_all(
char* str,
const char* substr) {
677 if (!str || !substr || !*substr)
return 0;
678 size_t sub_len = strlen(substr);
684 if (strncmp(r, substr, sub_len) == 0) {
707static inline void str_remove_substr(
char* str,
size_t start,
size_t slen) {
708 if (!str || slen == 0)
return;
709 size_t len = strlen(str);
710 if (start >= len)
return;
711 if (slen > len - start) slen = len - start;
713 size_t tail = len - start - slen;
715 memmove(str + start, str + start + slen, tail + 1);
734static inline char* str_dup(
const char* str) {
735 if (!str)
return NULL;
736 size_t len = strlen(str) + 1;
737 char* r = (
char*)malloc(len);
738 if (r) memcpy(r, str, len);
753static inline char* str_ndup(
const char* str,
size_t n) {
754 if (!str)
return NULL;
755 size_t len = strlen(str);
756 if (n < len) len = n;
757 char* r = (
char*)malloc(len + 1);
777static inline char* str_substr(
const char* str,
size_t start,
size_t length) {
778 if (!str)
return NULL;
779 size_t len = strlen(str);
780 if (start > len)
return NULL;
782 size_t avail = len - start;
783 size_t copy = (length > avail) ? avail : length;
785 char* r = (
char*)malloc(copy + 1);
787 memcpy(r, str + start, copy);
803static inline char* str_repeat(
const char* str,
size_t n) {
804 if (!str)
return NULL;
805 size_t slen = strlen(str);
806 size_t result = slen * n;
808 char* r = (
char*)malloc(result + 1);
811 for (
size_t i = 0; i < n; i++) memcpy(r + i * slen, str, slen);
828static inline char* str_pad_left(
const char* str,
size_t width,
char pad_char) {
829 if (!str)
return NULL;
830 size_t len = strlen(str);
831 if (len >= width)
return str_dup(str);
833 size_t pad = width - len;
834 char* r = (
char*)malloc(width + 1);
837 memset(r, (
unsigned char)pad_char, pad);
838 memcpy(r + pad, str, len);
855static inline char* str_pad_right(
const char* str,
size_t width,
char pad_char) {
856 if (!str)
return NULL;
857 size_t len = strlen(str);
858 if (len >= width)
return str_dup(str);
860 size_t pad = width - len;
861 char* r = (
char*)malloc(width + 1);
865 memset(r + len, (
unsigned char)pad_char, pad);
883static inline char* str_center(
const char* str,
size_t width,
char pad_char) {
884 if (!str)
return NULL;
885 size_t len = strlen(str);
886 if (len >= width)
return str_dup(str);
888 size_t total_pad = width - len;
889 size_t left_pad = total_pad / 2;
890 size_t right_pad = total_pad - left_pad;
892 char* r = (
char*)malloc(width + 1);
895 memset(r, (
unsigned char)pad_char, left_pad);
896 memcpy(r + left_pad, str, len);
897 memset(r + left_pad + len, (
unsigned char)pad_char, right_pad);
915static inline char* str_to_snakecase(
const char* str) {
916 if (!str)
return NULL;
917 size_t orig = strlen(str);
919 char* empty = (
char*)malloc(1);
920 if (empty) empty[0] =
'\0';
926 for (
size_t i = 1; i < orig; i++) {
927 unsigned char c = (
unsigned char)str[i];
928 if ((
unsigned)(c -
'A') <= 25u) {
929 unsigned char prev = (
unsigned char)str[i - 1];
930 unsigned char next = (i + 1 < orig) ? (
unsigned char)str[i + 1] :
'\0';
932 bool prev_is_lower = (unsigned)(prev -
'a') <= 25u;
933 bool next_is_lower = (unsigned)(next -
'a') <= 25u;
936 if (prev !=
'_' && (prev_is_lower || next_is_lower)) {
942 char* r = (
char*)malloc(orig + extra + 1);
947 for (
size_t i = 0; i < orig; i++) {
948 unsigned char c = (
unsigned char)str[i];
949 if ((
unsigned)(c -
'A') <= 25u) {
951 unsigned char prev = (
unsigned char)str[i - 1];
952 unsigned char next = (i + 1 < orig) ? (
unsigned char)str[i + 1] :
'\0';
954 bool prev_is_lower = (unsigned)(prev -
'a') <= 25u;
955 bool next_is_lower = (unsigned)(next -
'a') <= 25u;
957 if (prev !=
'_' && (prev_is_lower || next_is_lower)) {
961 r[w++] = (char)(c | 0x20u);
982static inline char* str_replace(
const char* str,
const char* old_str,
const char* new_str) {
983 if (!str)
return NULL;
984 if (!old_str || !new_str)
return str_dup(str);
986 size_t hlen = strlen(str);
987 size_t old_len = strlen(old_str);
988 if (old_len == 0)
return str_dup(str);
990 const char* found = str_search_impl(str, hlen, old_str, old_len);
991 if (!found)
return str_dup(str);
993 size_t new_len = strlen(new_str);
994 size_t prefix_len = (size_t)(found - str);
995 size_t suffix_len = hlen - prefix_len - old_len;
996 size_t result_len = prefix_len + new_len + suffix_len;
998 char* r = (
char*)malloc(result_len + 1);
1001 memcpy(r, str, prefix_len);
1002 memcpy(r + prefix_len, new_str, new_len);
1003 memcpy(r + prefix_len + new_len, found + old_len, suffix_len);
1004 r[result_len] =
'\0';
1021static inline char* str_replace_all(
const char* str,
const char* old_sub,
const char* new_sub) {
1022 if (!str)
return NULL;
1023 size_t hlen = strlen(str);
1024 if (!old_sub || !new_sub)
return str_dup(str);
1026 size_t old_len = strlen(old_sub);
1027 if (old_len == 0)
return str_dup(str);
1029 size_t new_len = strlen(new_sub);
1031#define STR_RA_STACK_CAP 64
1032 size_t stack_offs[STR_RA_STACK_CAP];
1033 size_t* offs = stack_offs;
1034 size_t offs_cap = STR_RA_STACK_CAP;
1037 const char* p = str;
1041 while ((p = str_search_impl(p, rem, old_sub, old_len)) != NULL) {
1042 if (count >= offs_cap) {
1043 if (offs_cap > SIZE_MAX / 2 /
sizeof(
size_t))
goto oom;
1044 size_t new_cap = offs_cap * 2;
1046 if (offs == stack_offs) {
1047 no = (
size_t*)malloc(new_cap *
sizeof(
size_t));
1049 memcpy(no, stack_offs, count *
sizeof(
size_t));
1051 no = (
size_t*)realloc(offs, new_cap *
sizeof(
size_t));
1057 offs[count++] = (size_t)(p - str);
1059 rem = hlen - (size_t)(p - str);
1063 if (offs != stack_offs) free(offs);
1064 return str_dup(str);
1069 if (new_len >= old_len)
1070 result_len = hlen + count * (new_len - old_len);
1072 result_len = hlen - count * (old_len - new_len);
1074 char* r = (
char*)malloc(result_len + 1);
1077 size_t wp = 0, sp = 0;
1078 for (
size_t i = 0; i < count; i++) {
1079 size_t gap = offs[i] - sp;
1081 memcpy(r + wp, str + sp, gap);
1085 memcpy(r + wp, new_sub, new_len);
1088 sp = offs[i] + old_len;
1090 size_t tail = hlen - sp;
1092 memcpy(r + wp, str + sp, tail);
1097 if (offs != stack_offs) free(offs);
1101 if (offs != stack_offs) free(offs);
1103#undef STR_RA_STACK_CAP
1124static inline char** str_split(
const char* str,
const char* delim,
size_t* count_out) {
1125 if (!count_out)
return NULL;
1127 if (!str)
return NULL;
1130 if (!delim || !*delim) {
1131 char** r = (
char**)malloc(2 *
sizeof(
char*));
1132 if (!r)
return NULL;
1133 r[0] = str_dup(str);
1143 size_t dlen = strlen(delim);
1145 char** result = (
char**)malloc(cap *
sizeof(
char*));
1146 if (!result)
return NULL;
1148 const char* start = str;
1149 const char* end = str + strlen(str);
1153 const char* found = str_search_impl(start, (
size_t)(end - start), delim, dlen);
1154 const char* tok_end = found ? found : end;
1157 if (count + 1 >= cap) {
1159 char** tmp = (
char**)realloc(result, cap *
sizeof(
char*));
1160 if (!tmp)
goto split_err;
1164 size_t tok_len = (size_t)(tok_end - start);
1165 result[count] = (
char*)malloc(tok_len + 1);
1166 if (!result[count])
goto split_err;
1167 memcpy(result[count], start, tok_len);
1168 result[count][tok_len] =
'\0';
1172 start = found + dlen;
1175 result[count] = NULL;
1180 for (
size_t i = 0; i < count; i++) free(result[i]);
1193static inline void str_free_split(
char** parts) {
1195 for (
size_t i = 0; parts[i]; i++) free(parts[i]);
1211static inline char* str_join(
const char** strings,
size_t count,
const char* delim) {
1212 if (!strings || count == 0) {
1213 char* empty = (
char*)malloc(1);
1214 if (empty) empty[0] =
'\0';
1218 size_t dlen = delim ? strlen(delim) : 0;
1220 for (
size_t i = 0; i < count; i++) {
1221 if (!strings[i])
return NULL;
1222 total += strlen(strings[i]);
1223 if (i + 1 < count) total += dlen;
1226 char* r = (
char*)malloc(total + 1);
1227 if (!r)
return NULL;
1230 for (
size_t i = 0; i < count; i++) {
1231 size_t len = strlen(strings[i]);
1233 memcpy(r + pos, strings[i], len);
1236 if (dlen && i + 1 < count) {
1237 memcpy(r + pos, delim, dlen);
1261static inline char* str_concat(
const char* first, ...) {
1266 va_start(ap, first);
1267 const char* s = first;
1270 s = va_arg(ap,
const char*);
1275 char* r = (
char*)malloc(total + 1);
1276 if (!r)
return NULL;
1282 va_start(ap, first);
1283 const char* s = first;
1285 size_t len = strlen(s);
1286 memcpy(r + pos, s, len);
1288 s = va_arg(ap,
const char*);
1305static inline uint32_t str_hash(
const char* str) {
1306 if (!str)
return 0u;
1307 uint32_t h = 2166136261u;
1308 for (; *str; str++) {
1309 h ^= (
unsigned char)*str;
1330static inline char* str_from_int(
int value,
char* buf,
size_t buflen) {
1331 if (!buf || buflen == 0)
return NULL;
1332 int need = snprintf(buf, buflen,
"%d", value);
1333 if (need < 0 || (
size_t)need >= buflen)
return NULL;
1348static inline char* str_from_long(
long value,
char* buf,
size_t buflen) {
1349 if (!buf || buflen == 0)
return NULL;
1350 int need = snprintf(buf, buflen,
"%ld", value);
1351 if (need < 0 || (
size_t)need >= buflen)
return NULL;
1368static inline char* str_from_double(
double value,
int precision,
char* buf,
size_t buflen) {
1369 if (!buf || buflen == 0)
return NULL;
1370 if (precision < 0) precision = 6;
1371 if (precision > 64) precision = 64;
1372 int need = snprintf(buf, buflen,
"%.*f", precision, value);
1373 if (need < 0 || (
size_t)need >= buflen)
return NULL;
1381#if defined(_MSC_VER)
1387static inline int strcasecmp(
const char* s1,
const char* s2) {
1388 if (s1 == s2)
return 0;
1391 return _stricmp(s1, s2);
1398static inline int strncasecmp(
const char* s1,
const char* s2,
size_t n) {
1399 if (n == 0)
return 0;
1400 if (s1 == s2)
return 0;
1403 return _strnicmp(s1, s2, n);
1413static inline char* strcasestr(
const char* haystack,
const char* needle) {
1414 if (!needle || *needle ==
'\0')
return (
char*)haystack;
1415 if (!haystack)
return NULL;
1416 const size_t nlen = strlen(needle);
1418 if (strncasecmp(haystack, needle, nlen) == 0)
return (
char*)haystack;