/* MIT License * * Copyright (c) 2025 Tyge Løvset * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // IWYU pragma: private, include "stc/cstr.h" #ifndef STC_CSTR_PRV_H_INCLUDED #define STC_CSTR_PRV_H_INCLUDED #include /* FILE*, vsnprintf */ #include /* malloc */ #include /* size_t */ #include /* cstr_vfmt() */ /**************************** PRIVATE API **********************************/ #if defined __GNUC__ && !defined __clang__ // linkage.h already does diagnostic push // Warns wrongfully on -O3 on cstr_assign(&str, "literal longer than 23 ..."); #pragma GCC diagnostic ignored "-Warray-bounds" #endif enum { cstr_s_cap = sizeof(cstr_buf) - 2 }; #define cstr_s_size(s) ((isize)(s)->sml.size) #define cstr_s_set_size(s, len) ((s)->sml.data[(s)->sml.size = (uint8_t)(len)] = 0) #define cstr_s_data(s) (s)->sml.data #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define byte_rotl_(x, b) ((x) << (b)*8 | (x) >> (sizeof(x) - (b))*8) #define cstr_l_cap(s) (isize)(~byte_rotl_((s)->lon.ncap, sizeof((s)->lon.ncap) - 1)) #define cstr_l_set_cap(s, cap) ((s)->lon.ncap = ~byte_rotl_((uintptr_t)(cap), 1)) #else #define cstr_l_cap(s) (isize)(~(s)->lon.ncap) #define cstr_l_set_cap(s, cap) ((s)->lon.ncap = ~(uintptr_t)(cap)) #endif #define cstr_l_size(s) (isize)((s)->lon.size) #define cstr_l_set_size(s, len) ((s)->lon.data[(s)->lon.size = (uintptr_t)(len)] = 0) #define cstr_l_data(s) (s)->lon.data #define cstr_l_drop(s) c_free((s)->lon.data, cstr_l_cap(s) + 1) #define cstr_is_long(s) ((s)->sml.size >= 128) extern char* _cstr_init(cstr* self, isize len, isize cap); extern char* _cstr_internal_move(cstr* self, isize pos1, isize pos2); /**************************** PUBLIC API **********************************/ #define cstr_init() (c_literal(cstr){0}) #define cstr_lit(literal) cstr_from_n(literal, c_litstrlen(literal)) extern cstr cstr_from_replace(csview sv, csview search, csview repl, int32_t count); extern cstr cstr_from_fmt(const char* fmt, ...) c_GNUATTR(format(printf, 1, 2)); extern void cstr_drop(const cstr* self); extern cstr* cstr_take(cstr* self, const cstr s); extern char* cstr_reserve(cstr* self, isize cap); extern void cstr_shrink_to_fit(cstr* self); extern char* cstr_resize(cstr* self, isize size, char value); extern isize cstr_find_at(const cstr* self, isize pos, const char* search); extern isize cstr_find_sv(const cstr* self, csview search); extern char* cstr_assign_n(cstr* self, const char* str, isize len); extern char* cstr_append_n(cstr* self, const char* str, isize len); extern isize cstr_append_fmt(cstr* self, const char* fmt, ...) c_GNUATTR(format(printf, 2, 3)); extern char* cstr_append_uninit(cstr *self, isize len); extern bool cstr_getdelim(cstr *self, int delim, FILE *fp); extern void cstr_erase(cstr* self, isize pos, isize len); extern isize cstr_printf(cstr* self, const char* fmt, ...) c_GNUATTR(format(printf, 2, 3)); extern isize cstr_vfmt(cstr* self, isize start, const char* fmt, va_list args); extern size_t cstr_hash(const cstr *self); extern bool cstr_u8_valid(const cstr* self); extern void cstr_u8_erase(cstr* self, isize u8pos, isize u8len); STC_INLINE cstr_buf cstr_getbuf(cstr* s) { return cstr_is_long(s) ? c_literal(cstr_buf){s->lon.data, cstr_l_size(s), cstr_l_cap(s)} : c_literal(cstr_buf){s->sml.data, cstr_s_size(s), cstr_s_cap}; } STC_INLINE zsview cstr_zv(const cstr* s) { return cstr_is_long(s) ? c_literal(zsview){s->lon.data, cstr_l_size(s)} : c_literal(zsview){s->sml.data, cstr_s_size(s)}; } STC_INLINE csview cstr_sv(const cstr* s) { return cstr_is_long(s) ? c_literal(csview){s->lon.data, cstr_l_size(s)} : c_literal(csview){s->sml.data, cstr_s_size(s)}; } STC_INLINE cstr cstr_from_n(const char* str, const isize len) { cstr s; c_memcpy(_cstr_init(&s, len, len), str, len); return s; } STC_INLINE cstr cstr_from(const char* str) { return cstr_from_n(str, c_strlen(str)); } STC_INLINE cstr cstr_from_sv(csview sv) { return cstr_from_n(sv.buf, sv.size); } STC_INLINE cstr cstr_from_zv(zsview zv) { return cstr_from_n(zv.str, zv.size); } STC_INLINE cstr cstr_with_size(const isize size, const char value) { cstr s; c_memset(_cstr_init(&s, size, size), value, size); return s; } STC_INLINE cstr cstr_with_capacity(const isize cap) { cstr s; _cstr_init(&s, 0, cap); return s; } STC_INLINE cstr cstr_move(cstr* self) { cstr tmp = *self; *self = cstr_init(); return tmp; } STC_INLINE cstr cstr_clone(cstr s) { csview sv = cstr_sv(&s); return cstr_from_n(sv.buf, sv.size); } #define SSO_CALL(s, call) (cstr_is_long(s) ? cstr_l_##call : cstr_s_##call) STC_INLINE void _cstr_set_size(cstr* self, isize len) { SSO_CALL(self, set_size(self, len)); } STC_INLINE void cstr_clear(cstr* self) { _cstr_set_size(self, 0); } STC_INLINE char* cstr_data(cstr* self) { return SSO_CALL(self, data(self)); } STC_INLINE const char* cstr_str(const cstr* self) { return SSO_CALL(self, data(self)); } STC_INLINE const char* cstr_toraw(const cstr* self) { return SSO_CALL(self, data(self)); } STC_INLINE isize cstr_size(const cstr* self) { return SSO_CALL(self, size(self)); } STC_INLINE bool cstr_is_empty(const cstr* self) { return cstr_size(self) == 0; } STC_INLINE isize cstr_capacity(const cstr* self) { return cstr_is_long(self) ? cstr_l_cap(self) : cstr_s_cap; } STC_INLINE isize cstr_to_index(const cstr* self, cstr_iter it) { return it.ref - cstr_str(self); } STC_INLINE cstr cstr_from_s(cstr s, isize pos, isize len) { return cstr_from_n(cstr_str(&s) + pos, len); } STC_INLINE csview cstr_subview(const cstr* self, isize pos, isize len) { csview sv = cstr_sv(self); c_assert(((size_t)pos <= (size_t)sv.size) & (len >= 0)); if (pos + len > sv.size) len = sv.size - pos; return c_literal(csview){sv.buf + pos, len}; } STC_INLINE zsview cstr_tail(const cstr* self, isize len) { c_assert(len >= 0); csview sv = cstr_sv(self); if (len > sv.size) len = sv.size; return c_literal(zsview){&sv.buf[sv.size - len], len}; } // BEGIN utf8 functions ===== STC_INLINE cstr cstr_u8_from(const char* str, isize u8pos, isize u8len) { str = utf8_at(str, u8pos); return cstr_from_n(str, utf8_to_index(str, u8len)); } STC_INLINE isize cstr_u8_size(const cstr* self) { return utf8_count(cstr_str(self)); } STC_INLINE isize cstr_u8_to_index(const cstr* self, isize u8pos) { return utf8_to_index(cstr_str(self), u8pos); } STC_INLINE zsview cstr_u8_tail(const cstr* self, isize u8len) { csview sv = cstr_sv(self); const char* p = &sv.buf[sv.size]; while (u8len && p != sv.buf) u8len -= (*--p & 0xC0) != 0x80; return c_literal(zsview){p, sv.size - (p - sv.buf)}; } STC_INLINE csview cstr_u8_subview(const cstr* self, isize u8pos, isize u8len) { return utf8_subview(cstr_str(self), u8pos, u8len); } STC_INLINE cstr_iter cstr_u8_at(const cstr* self, isize u8pos) { csview sv; sv.buf = utf8_at(cstr_str(self), u8pos); sv.size = utf8_chr_size(sv.buf); c_assert(sv.size); return c_literal(cstr_iter){.chr = sv}; } // utf8 iterator STC_INLINE cstr_iter cstr_begin(const cstr* self) { csview sv = cstr_sv(self); cstr_iter it = {.chr = {sv.buf, utf8_chr_size(sv.buf)}}; return it; } STC_INLINE cstr_iter cstr_end(const cstr* self) { (void)self; cstr_iter it = {0}; return it; } STC_INLINE void cstr_next(cstr_iter* it) { it->ref += it->chr.size; it->chr.size = utf8_chr_size(it->ref); if (*it->ref == '\0') it->ref = NULL; } STC_INLINE cstr_iter cstr_advance(cstr_iter it, isize u8pos) { it.ref = utf8_offset(it.ref, u8pos); it.chr.size = utf8_chr_size(it.ref); if (*it.ref == '\0') it.ref = NULL; return it; } // utf8 case conversion: requires `#define i_import` before including cstr.h in one TU. extern cstr cstr_tocase_sv(csview sv, int k); STC_INLINE cstr cstr_casefold_sv(csview sv) { return cstr_tocase_sv(sv, 0); } STC_INLINE cstr cstr_tolower_sv(csview sv) { return cstr_tocase_sv(sv, 1); } STC_INLINE cstr cstr_toupper_sv(csview sv) { return cstr_tocase_sv(sv, 2); } STC_INLINE cstr cstr_tolower(const char* str) { return cstr_tolower_sv(c_sv(str, c_strlen(str))); } STC_INLINE cstr cstr_toupper(const char* str) { return cstr_toupper_sv(c_sv(str, c_strlen(str))); } STC_INLINE void cstr_lowercase(cstr* self) { cstr_take(self, cstr_tolower_sv(cstr_sv(self))); } STC_INLINE void cstr_uppercase(cstr* self) { cstr_take(self, cstr_toupper_sv(cstr_sv(self))); } STC_INLINE bool cstr_istarts_with(const cstr* self, const char* sub) { csview sv = cstr_sv(self); isize len = c_strlen(sub); return len <= sv.size && !utf8_icompare((sv.size = len, sv), c_sv(sub, len)); } STC_INLINE bool cstr_iends_with(const cstr* self, const char* sub) { csview sv = cstr_sv(self); isize len = c_strlen(sub); return len <= sv.size && !utf8_icmp(sv.buf + sv.size - len, sub); } STC_INLINE int cstr_icmp(const cstr* s1, const cstr* s2) { return utf8_icmp(cstr_str(s1), cstr_str(s2)); } STC_INLINE bool cstr_ieq(const cstr* s1, const cstr* s2) { csview x = cstr_sv(s1), y = cstr_sv(s2); return x.size == y.size && !utf8_icompare(x, y); } STC_INLINE bool cstr_iequals(const cstr* self, const char* str) { return !utf8_icmp(cstr_str(self), str); } // END utf8 ===== STC_INLINE int cstr_cmp(const cstr* s1, const cstr* s2) { return strcmp(cstr_str(s1), cstr_str(s2)); } STC_INLINE bool cstr_eq(const cstr* s1, const cstr* s2) { csview x = cstr_sv(s1), y = cstr_sv(s2); return x.size == y.size && !c_memcmp(x.buf, y.buf, x.size); } STC_INLINE bool cstr_equals(const cstr* self, const char* str) { return !strcmp(cstr_str(self), str); } STC_INLINE bool cstr_equals_sv(const cstr* self, csview sv) { return sv.size == cstr_size(self) && !c_memcmp(cstr_str(self), sv.buf, sv.size); } STC_INLINE isize cstr_find(const cstr* self, const char* search) { const char *str = cstr_str(self), *res = strstr((char*)str, search); return res ? (res - str) : c_NPOS; } STC_INLINE bool cstr_contains(const cstr* self, const char* search) { return strstr((char*)cstr_str(self), search) != NULL; } STC_INLINE bool cstr_contains_sv(const cstr* self, csview search) { return cstr_find_sv(self, search) != c_NPOS; } STC_INLINE bool cstr_starts_with_sv(const cstr* self, csview sub) { if (sub.size > cstr_size(self)) return false; return !c_memcmp(cstr_str(self), sub.buf, sub.size); } STC_INLINE bool cstr_starts_with(const cstr* self, const char* sub) { const char* str = cstr_str(self); while (*sub && *str == *sub) ++str, ++sub; return !*sub; } STC_INLINE bool cstr_ends_with_sv(const cstr* self, csview sub) { csview sv = cstr_sv(self); if (sub.size > sv.size) return false; return !c_memcmp(sv.buf + sv.size - sub.size, sub.buf, sub.size); } STC_INLINE bool cstr_ends_with(const cstr* self, const char* sub) { return cstr_ends_with_sv(self, c_sv(sub, c_strlen(sub))); } STC_INLINE char* cstr_assign(cstr* self, const char* str) { return cstr_assign_n(self, str, c_strlen(str)); } STC_INLINE char* cstr_assign_sv(cstr* self, csview sv) { return cstr_assign_n(self, sv.buf, sv.size); } STC_INLINE char* cstr_copy(cstr* self, cstr s) { csview sv = cstr_sv(&s); return cstr_assign_n(self, sv.buf, sv.size); } STC_INLINE char* cstr_push(cstr* self, const char* chr) { return cstr_append_n(self, chr, utf8_chr_size(chr)); } STC_INLINE void cstr_pop(cstr* self) { csview sv = cstr_sv(self); const char* s = sv.buf + sv.size; while ((*--s & 0xC0) == 0x80) ; _cstr_set_size(self, (s - sv.buf)); } STC_INLINE char* cstr_append(cstr* self, const char* str) { return cstr_append_n(self, str, c_strlen(str)); } STC_INLINE char* cstr_append_sv(cstr* self, csview sv) { return cstr_append_n(self, sv.buf, sv.size); } STC_INLINE char* cstr_append_s(cstr* self, cstr s) { return cstr_append_sv(self, cstr_sv(&s)); } #define cstr_join(self, sep, vec) do { \ struct _vec_s { cstr* data; ptrdiff_t size; } \ *_vec = (struct _vec_s*)&(vec); \ (void)sizeof((vec).data == _vec->data && &(vec).size == &_vec->size); \ cstr_join_sn(self, sep, _vec->data, _vec->size); \ } while (0); #define cstr_join_items(self, sep, ...) \ cstr_join_n(self, sep, c_make_array(const char*, __VA_ARGS__), c_sizeof((const char*[])__VA_ARGS__)/c_sizeof(char*)) STC_INLINE void cstr_join_n(cstr* self, const char* sep, const char* arr[], isize n) { const char* _sep = cstr_is_empty(self) ? "" : sep; while (n--) { cstr_append(self, _sep); cstr_append(self, *arr++); _sep = sep; } } STC_INLINE void cstr_join_sn(cstr* self, const char* sep, const cstr arr[], isize n) { const char* _sep = cstr_is_empty(self) ? "" : sep; while (n--) { cstr_append(self, _sep); cstr_append_s(self, *arr++); _sep = sep; } } STC_INLINE void cstr_replace_sv(cstr* self, csview search, csview repl, int32_t count) { cstr_take(self, cstr_from_replace(cstr_sv(self), search, repl, count)); } STC_INLINE void cstr_replace_nfirst(cstr* self, const char* search, const char* repl, int32_t count) { cstr_replace_sv(self, c_sv(search, c_strlen(search)), c_sv(repl, c_strlen(repl)), count); } STC_INLINE void cstr_replace(cstr* self, const char* search, const char* repl) { cstr_replace_nfirst(self, search, repl, INT32_MAX); } STC_INLINE void cstr_replace_at_sv(cstr* self, isize pos, isize len, const csview repl) { char* d = _cstr_internal_move(self, pos + len, pos + repl.size); c_memcpy(d + pos, repl.buf, repl.size); } STC_INLINE void cstr_replace_at(cstr* self, isize pos, isize len, const char* repl) { cstr_replace_at_sv(self, pos, len, c_sv(repl, c_strlen(repl))); } STC_INLINE void cstr_u8_replace(cstr* self, isize u8pos, isize u8len, const char* repl) { const char* s = cstr_str(self); csview span = utf8_subview(s, u8pos, u8len); cstr_replace_at(self, span.buf - s, span.size, repl); } STC_INLINE void cstr_insert_sv(cstr* self, isize pos, csview sv) { cstr_replace_at_sv(self, pos, 0, sv); } STC_INLINE void cstr_insert(cstr* self, isize pos, const char* str) { cstr_replace_at_sv(self, pos, 0, c_sv(str, c_strlen(str))); } STC_INLINE void cstr_u8_insert(cstr* self, isize u8pos, const char* str) { cstr_insert(self, utf8_to_index(cstr_str(self), u8pos), str); } STC_INLINE bool cstr_getline(cstr *self, FILE *fp) { return cstr_getdelim(self, '\n', fp); } #endif // STC_CSTR_PRV_H_INCLUDED