solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
str.h
Go to the documentation of this file.
1
32#ifndef __STR_H__
33#define __STR_H__
34
35#include <ctype.h>
36#include <errno.h>
37#include <limits.h>
38#include <stdarg.h>
39#include <stdbool.h>
40#include <stddef.h>
41#include <stdint.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45
46#ifdef __cplusplus
47extern "C" {
48#endif
49
50/* =========================================================================
51 * Internal fast search
52 * ======================================================================= */
53
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);
75
76 const char* cur = hs;
77 const char* end = hs + hlen - nlen;
78 unsigned char first = (unsigned char)nd[0];
79 unsigned char last = (unsigned char)nd[nlen - 1];
80
81 while (cur <= end) {
82 cur = (const char*)memchr(cur, first, (size_t)(end - cur + 1));
83 if (!cur) return NULL;
84
85 if ((unsigned char)cur[nlen - 1] == last) {
86 if (nlen <= 9) {
87 /* Unrolled inner comparison avoids memcmp call overhead. */
88 size_t i = 0;
89 for (; i < nlen - 2; i++) {
90 if (cur[1 + i] != nd[1 + i]) goto next_iter;
91 }
92 return cur;
93 } else {
94 if (memcmp(cur + 1, nd + 1, nlen - 2) == 0) return cur;
95 }
96 }
97 next_iter:
98 cur++;
99 }
100 return NULL;
101}
102
103/* =========================================================================
104 * Predicates — O(n), no allocation
105 * ======================================================================= */
106
116static inline bool str_is_empty(const char* str) { return !str || str[0] == '\0'; }
117
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;
129 }
130 return true;
131}
132
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;
145 }
146 return true;
147}
148
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;
161 }
162 return true;
163}
164
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;
177 }
178 return true;
179}
180
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; /* bare sign */
193 for (; *str; str++) {
194 if (!isdigit((unsigned char)*str)) return false;
195 }
196 return true;
197}
198
207static inline bool str_is_float(const char* str) {
208 if (!str || !*str) return false;
209 char* end;
210 errno = 0;
211 (void)strtod(str, &end);
212 return *end == '\0' && end != str;
213}
214
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;
226}
227
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;
240 }
241 return *a == '\0' && *b == '\0';
242}
243
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;
258}
259
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;
273}
274
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;
290}
291
292/* =========================================================================
293 * Search & position — O(n), no allocation
294 * ======================================================================= */
295
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;
310}
311
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;
328
329 const char* last = NULL;
330 const char* p = str;
331
332 while ((p = str_search_impl(p, hlen - (size_t)(p - str), substr, nlen)) != NULL) {
333 last = p++;
334 /* Once there is no room left for another match, stop early. */
335 if ((size_t)(p - str) + nlen > hlen) break;
336 }
337 return last ? (int)(last - str) : -1;
338}
339
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;
356
357 size_t count = 0;
358 const char* p = str;
359 size_t rem = hlen;
360
361 while ((p = str_search_impl(p, rem, substr, nlen)) != NULL) {
362 count++;
363 p += nlen;
364 rem = hlen - (size_t)(p - str);
365 if (rem < nlen) break;
366 }
367 return count;
368}
369
380static inline size_t str_word_count(const char* str) {
381 if (!str) return 0;
382 size_t count = 0;
383 bool in_word = false;
384 for (; *str; str++) {
385 if (isspace((unsigned char)*str)) {
386 in_word = false;
387 } else if (!in_word) {
388 in_word = true;
389 count++;
390 }
391 }
392 return count;
393}
394
395/* =========================================================================
396 * In-place case conversion — O(n), no allocation
397 * ======================================================================= */
398
407static inline void str_lower(char* str) {
408 if (!str) return;
409 for (; *str; str++) {
410 unsigned char c = (unsigned char)*str;
411 if ((unsigned)(c - 'A') <= 25u) *str = (char)(c | 0x20u);
412 }
413}
414
423static inline void str_upper(char* str) {
424 if (!str) return;
425 for (; *str; str++) {
426 unsigned char c = (unsigned char)*str;
427 if ((unsigned)(c - 'a') <= 25u) *str = (char)(c & ~0x20u);
428 }
429}
430
439static inline void str_capitalize(char* str) {
440 if (!str || !*str) return;
441 *str = (char)toupper((unsigned char)*str);
442 str++;
443 for (; *str; str++) *str = (char)tolower((unsigned char)*str);
444}
445
456static inline void str_camelcase(char* str) {
457 if (!str || !*str) return;
458
459 size_t r = 0;
460 size_t w = 0;
461 size_t len = strlen(str);
462
463 /* Skip leading underscores / spaces */
464 while (r < len && (str[r] == '_' || isspace((unsigned char)str[r]))) r++;
465
466 /* First character is lowercased */
467 if (r < len) {
468 unsigned char c = (unsigned char)str[r++];
469 str[w++] = (char)((unsigned)(c - 'A') <= 25u ? (c | 0x20u) : c);
470 }
471
472 bool cap = false;
473 while (r < len) {
474 unsigned char c = (unsigned char)str[r++];
475 if (c == '_' || isspace(c)) {
476 cap = true;
477 } else if (cap) {
478 str[w++] = (char)toupper(c);
479 cap = false;
480 } else {
481 str[w++] = (char)tolower(c);
482 }
483 }
484 str[w] = '\0';
485}
486
495static inline void str_pascalcase(char* str) {
496 if (!str || !*str) return;
497
498 size_t r = 0;
499 size_t w = 0;
500 size_t len = strlen(str);
501
502 /* Skip leading underscores / spaces */
503 while (r < len && (str[r] == '_' || isspace((unsigned char)str[r]))) r++;
504
505 bool new_word = true;
506 while (r < len) {
507 unsigned char c = (unsigned char)str[r++];
508 if (c == '_' || isspace(c)) {
509 new_word = true;
510 } else {
511 str[w++] = new_word ? (char)toupper(c) : (char)tolower(c);
512 new_word = false;
513 }
514 }
515 str[w] = '\0';
516}
517
528static inline void str_titlecase(char* str) {
529 if (!str) return;
530 bool cap = true;
531 for (; *str; str++) {
532 unsigned char c = (unsigned char)*str;
533 if (isspace(c)) {
534 cap = true;
535 } else if (cap) {
536 *str = (char)toupper(c);
537 cap = false;
538 } else {
539 *str = (char)tolower(c);
540 }
541 }
542}
543
544/* =========================================================================
545 * In-place trimming, reversal & removal — O(n), no allocation
546 * ======================================================================= */
547
556static inline void str_ltrim(char* str) {
557 if (!str || !*str) return;
558 size_t len = strlen(str);
559 size_t start = 0;
560 while (start < len && isspace((unsigned char)str[start])) start++;
561 if (start == 0) return;
562 memmove(str, str + start, len - start + 1);
563}
564
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--;
575 str[len] = '\0';
576}
577
586static inline void str_trim(char* str) {
587 if (!str || !*str) return;
588 str_rtrim(str);
589 str_ltrim(str);
590}
591
601static inline void str_trim_chars(char* str, const char* chars) {
602 if (!str || !chars || !*chars) return;
603 size_t len = strlen(str);
604 size_t start = 0;
605
606 while (start < len && strchr(chars, str[start])) start++;
607 if (start == len) {
608 str[0] = '\0';
609 return;
610 }
611
612 size_t end = len - 1;
613 while (end > start && strchr(chars, str[end])) end--;
614
615 size_t new_len = end - start + 1;
616 if (start) memmove(str, str + start, new_len);
617 str[new_len] = '\0';
618}
619
629static inline void str_truncate(char* str, size_t max_len) {
630 if (!str) return;
631 size_t len = strlen(str);
632 if (len > max_len) str[max_len] = '\0';
633}
634
640static inline void str_reverse(char* str) {
641 if (!str) return;
642 size_t len = strlen(str);
643 if (len < 2) return;
644 for (size_t i = 0, j = len - 1; i < j; i++, j--) {
645 char t = str[i];
646 str[i] = str[j];
647 str[j] = t;
648 }
649}
650
658static inline void str_remove_char(char* str, char c) {
659 if (!str) return;
660 char *w = str, *r = str;
661 while (*r) {
662 if (*r != c) *w++ = *r;
663 r++;
664 }
665 *w = '\0';
666}
667
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);
679 char* w = str;
680 char* r = str;
681 size_t count = 0;
682
683 while (*r) {
684 if (strncmp(r, substr, sub_len) == 0) {
685 r += sub_len;
686 count++;
687 } else {
688 *w++ = *r++;
689 }
690 }
691 *w = '\0';
692 return count;
693}
694
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;
712
713 size_t tail = len - start - slen;
714 if (tail > 0)
715 memmove(str + start, str + start + slen, tail + 1);
716 else
717 str[start] = '\0';
718}
719
720/* =========================================================================
721 * Allocating helpers — caller must free() every returned pointer
722 * ======================================================================= */
723
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);
739 return r;
740}
741
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);
758 if (!r) return NULL;
759 memcpy(r, str, len);
760 r[len] = '\0';
761 return r;
762}
763
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;
781
782 size_t avail = len - start;
783 size_t copy = (length > avail) ? avail : length;
784
785 char* r = (char*)malloc(copy + 1);
786 if (!r) return NULL;
787 memcpy(r, str + start, copy);
788 r[copy] = '\0';
789 return r;
790}
791
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; /* 0 when n == 0 */
807
808 char* r = (char*)malloc(result + 1);
809 if (!r) return NULL;
810
811 for (size_t i = 0; i < n; i++) memcpy(r + i * slen, str, slen);
812 r[result] = '\0';
813 return r;
814}
815
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);
832
833 size_t pad = width - len;
834 char* r = (char*)malloc(width + 1);
835 if (!r) return NULL;
836
837 memset(r, (unsigned char)pad_char, pad);
838 memcpy(r + pad, str, len);
839 r[width] = '\0';
840 return r;
841}
842
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);
859
860 size_t pad = width - len;
861 char* r = (char*)malloc(width + 1);
862 if (!r) return NULL;
863
864 memcpy(r, str, len);
865 memset(r + len, (unsigned char)pad_char, pad);
866 r[width] = '\0';
867 return r;
868}
869
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);
887
888 size_t total_pad = width - len;
889 size_t left_pad = total_pad / 2;
890 size_t right_pad = total_pad - left_pad;
891
892 char* r = (char*)malloc(width + 1);
893 if (!r) return NULL;
894
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);
898 r[width] = '\0';
899 return r;
900}
901
915static inline char* str_to_snakecase(const char* str) {
916 if (!str) return NULL;
917 size_t orig = strlen(str);
918 if (orig == 0) {
919 char* empty = (char*)malloc(1);
920 if (empty) empty[0] = '\0';
921 return empty;
922 }
923
924 /* Pass 1: Count how many underscores we need to insert */
925 size_t extra = 0;
926 for (size_t i = 1; i < orig; i++) {
927 unsigned char c = (unsigned char)str[i];
928 if ((unsigned)(c - 'A') <= 25u) { // If current is uppercase
929 unsigned char prev = (unsigned char)str[i - 1];
930 unsigned char next = (i + 1 < orig) ? (unsigned char)str[i + 1] : '\0';
931
932 bool prev_is_lower = (unsigned)(prev - 'a') <= 25u;
933 bool next_is_lower = (unsigned)(next - 'a') <= 25u;
934
935 // Insert '_' if transitioning from lower->UPPER or UPPER->UPPER->lower
936 if (prev != '_' && (prev_is_lower || next_is_lower)) {
937 extra++;
938 }
939 }
940 }
941
942 char* r = (char*)malloc(orig + extra + 1);
943 if (!r) return NULL;
944
945 /* Pass 2: Build the string */
946 size_t w = 0;
947 for (size_t i = 0; i < orig; i++) {
948 unsigned char c = (unsigned char)str[i];
949 if ((unsigned)(c - 'A') <= 25u) { // If current is uppercase
950 if (i > 0) {
951 unsigned char prev = (unsigned char)str[i - 1];
952 unsigned char next = (i + 1 < orig) ? (unsigned char)str[i + 1] : '\0';
953
954 bool prev_is_lower = (unsigned)(prev - 'a') <= 25u;
955 bool next_is_lower = (unsigned)(next - 'a') <= 25u;
956
957 if (prev != '_' && (prev_is_lower || next_is_lower)) {
958 r[w++] = '_';
959 }
960 }
961 r[w++] = (char)(c | 0x20u); // Convert to lowercase
962 } else {
963 r[w++] = (char)c; // Keep as is
964 }
965 }
966 r[w] = '\0';
967
968 return r;
969}
970
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);
985
986 size_t hlen = strlen(str);
987 size_t old_len = strlen(old_str);
988 if (old_len == 0) return str_dup(str);
989
990 const char* found = str_search_impl(str, hlen, old_str, old_len);
991 if (!found) return str_dup(str);
992
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;
997
998 char* r = (char*)malloc(result_len + 1);
999 if (!r) return NULL;
1000
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';
1005 return r;
1006}
1007
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);
1025
1026 size_t old_len = strlen(old_sub);
1027 if (old_len == 0) return str_dup(str);
1028
1029 size_t new_len = strlen(new_sub);
1030
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;
1035 size_t count = 0;
1036
1037 const char* p = str;
1038 size_t rem = hlen;
1039
1040 /* Pass 1 – collect match offsets */
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;
1045 size_t* no;
1046 if (offs == stack_offs) {
1047 no = (size_t*)malloc(new_cap * sizeof(size_t));
1048 if (!no) goto oom;
1049 memcpy(no, stack_offs, count * sizeof(size_t));
1050 } else {
1051 no = (size_t*)realloc(offs, new_cap * sizeof(size_t));
1052 if (!no) goto oom;
1053 }
1054 offs = no;
1055 offs_cap = new_cap;
1056 }
1057 offs[count++] = (size_t)(p - str);
1058 p += old_len;
1059 rem = hlen - (size_t)(p - str);
1060 }
1061
1062 if (count == 0) {
1063 if (offs != stack_offs) free(offs);
1064 return str_dup(str);
1065 }
1066
1067 /* Pass 2 – build result */
1068 size_t result_len;
1069 if (new_len >= old_len)
1070 result_len = hlen + count * (new_len - old_len);
1071 else
1072 result_len = hlen - count * (old_len - new_len);
1073
1074 char* r = (char*)malloc(result_len + 1);
1075 if (!r) goto oom;
1076
1077 size_t wp = 0, sp = 0;
1078 for (size_t i = 0; i < count; i++) {
1079 size_t gap = offs[i] - sp;
1080 if (gap) {
1081 memcpy(r + wp, str + sp, gap);
1082 wp += gap;
1083 }
1084 if (new_len) {
1085 memcpy(r + wp, new_sub, new_len);
1086 wp += new_len;
1087 }
1088 sp = offs[i] + old_len;
1089 }
1090 size_t tail = hlen - sp;
1091 if (tail) {
1092 memcpy(r + wp, str + sp, tail);
1093 wp += tail;
1094 }
1095 r[wp] = '\0';
1096
1097 if (offs != stack_offs) free(offs);
1098 return r;
1099
1100oom:
1101 if (offs != stack_offs) free(offs);
1102 return NULL;
1103#undef STR_RA_STACK_CAP
1104}
1105
1124static inline char** str_split(const char* str, const char* delim, size_t* count_out) {
1125 if (!count_out) return NULL;
1126 *count_out = 0;
1127 if (!str) return NULL;
1128
1129 /* No delimiter → single token */
1130 if (!delim || !*delim) {
1131 char** r = (char**)malloc(2 * sizeof(char*));
1132 if (!r) return NULL;
1133 r[0] = str_dup(str);
1134 if (!r[0]) {
1135 free(r);
1136 return NULL;
1137 }
1138 r[1] = NULL;
1139 *count_out = 1;
1140 return r;
1141 }
1142
1143 size_t dlen = strlen(delim);
1144 size_t cap = 8;
1145 char** result = (char**)malloc(cap * sizeof(char*));
1146 if (!result) return NULL;
1147
1148 const char* start = str;
1149 const char* end = str + strlen(str);
1150 size_t count = 0;
1151
1152 for (;;) {
1153 const char* found = str_search_impl(start, (size_t)(end - start), delim, dlen);
1154 const char* tok_end = found ? found : end;
1155
1156 /* Keep one extra slot for the NULL terminator */
1157 if (count + 1 >= cap) {
1158 cap *= 2;
1159 char** tmp = (char**)realloc(result, cap * sizeof(char*));
1160 if (!tmp) goto split_err;
1161 result = tmp;
1162 }
1163
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';
1169 count++;
1170
1171 if (!found) break;
1172 start = found + dlen;
1173 }
1174
1175 result[count] = NULL;
1176 *count_out = count;
1177 return result;
1178
1179split_err:
1180 for (size_t i = 0; i < count; i++) free(result[i]);
1181 free(result);
1182 return NULL;
1183}
1184
1193static inline void str_free_split(char** parts) {
1194 if (!parts) return;
1195 for (size_t i = 0; parts[i]; i++) free(parts[i]);
1196 free(parts);
1197}
1198
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';
1215 return empty;
1216 }
1217
1218 size_t dlen = delim ? strlen(delim) : 0;
1219 size_t total = 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;
1224 }
1225
1226 char* r = (char*)malloc(total + 1);
1227 if (!r) return NULL;
1228
1229 size_t pos = 0;
1230 for (size_t i = 0; i < count; i++) {
1231 size_t len = strlen(strings[i]);
1232 if (len) {
1233 memcpy(r + pos, strings[i], len);
1234 pos += len;
1235 }
1236 if (dlen && i + 1 < count) {
1237 memcpy(r + pos, delim, dlen);
1238 pos += dlen;
1239 }
1240 }
1241 r[pos] = '\0';
1242 return r;
1243}
1244
1261static inline char* str_concat(const char* first, ...) {
1262 /* Pass 1 – measure total length */
1263 size_t total = 0;
1264 {
1265 va_list ap;
1266 va_start(ap, first);
1267 const char* s = first;
1268 while (s) {
1269 total += strlen(s);
1270 s = va_arg(ap, const char*);
1271 }
1272 va_end(ap);
1273 }
1274
1275 char* r = (char*)malloc(total + 1);
1276 if (!r) return NULL;
1277
1278 /* Pass 2 – copy */
1279 size_t pos = 0;
1280 {
1281 va_list ap;
1282 va_start(ap, first);
1283 const char* s = first;
1284 while (s) {
1285 size_t len = strlen(s);
1286 memcpy(r + pos, s, len);
1287 pos += len;
1288 s = va_arg(ap, const char*);
1289 }
1290 va_end(ap);
1291 }
1292 r[pos] = '\0';
1293 return r;
1294}
1295
1305static inline uint32_t str_hash(const char* str) {
1306 if (!str) return 0u;
1307 uint32_t h = 2166136261u; /* FNV offset basis */
1308 for (; *str; str++) {
1309 h ^= (unsigned char)*str;
1310 h *= 16777619u; /* FNV prime */
1311 }
1312 return h;
1313}
1314
1315/* =========================================================================
1316 * Number -> string conversions
1317 * ======================================================================= */
1318
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;
1334 return buf;
1335}
1336
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;
1352 return buf;
1353}
1354
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;
1374 return buf;
1375}
1376
1377/* =========================================================================
1378 * Legacy / platform helpers
1379 * ======================================================================= */
1380
1381#if defined(_MSC_VER)
1382
1387static inline int strcasecmp(const char* s1, const char* s2) {
1388 if (s1 == s2) return 0;
1389 if (!s1) return -1;
1390 if (!s2) return 1;
1391 return _stricmp(s1, s2);
1392}
1393
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;
1401 if (!s1) return -1;
1402 if (!s2) return 1;
1403 return _strnicmp(s1, s2, n);
1404}
1405
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);
1417 while (*haystack) {
1418 if (strncasecmp(haystack, needle, nlen) == 0) return (char*)haystack;
1419 haystack++;
1420 }
1421 return NULL;
1422}
1423#endif /* _MSC_VER */
1424
1425#ifdef __cplusplus
1426}
1427#endif
1428
1429#endif /* __STR_H__ */