Here is a small example that shows how to create, use and destroy a cstring:
#include "cstring.h"
int main(void) {
// create a cstring from a string literal
cstring str = cstr_init("hello");
cstr_println(str);
// append to it
cstr_append(&str, " world");
cstr_println(str);
// size and capacity
printf("size: %zu\n", cstr_size(str));
printf("cap: %zu\n", cstr_capacity(str));
// insert at index
cstr_insert(&str, 5, ",");
cstr_println(str);
// find a character
size_t pos = cstr_find(str, 'w');
printf("'w' at index: %zu\n", pos);
// substring
cstring sub = cstr_substr(str, 0, 5);
cstr_println(sub);
cstr_free(&sub);
// free the string
cstr_free(&str);
return 0;
}
Every function uses pvt_buf_grow_ to allocate or resize the buffer. In cstring.c:
static cstring pvt_buf_grow_(cstring ptr, size_t count)
{
size_t cap_001_ = (ptr) ?
CSTRMAX(count, pvt_total_cap_(ptr) << 1) :
CSTRMAX(count, CSTRING_DEFAULT_CAP);
cap_001_ = CSTRMIN(cap_001_, cstr_max_size());
size_t tmp_size_001_ = ((cap_001_ + 1) * sizeof(*ptr))
+ sizeof(pvt_cstr_metadata_t);
pvt_cstr_metadata_t *base_ptr_001_;
if(ptr){
pvt_cstr_assert(pvt_is_malloced_(ptr),
"Literals(const char[]) are not modifiable");
base_ptr_001_ = (pvt_cstr_metadata_t*)pvt_cstr_realloc(
pvt_dat_to_mdata_(ptr), tmp_size_001_);
}
else {
base_ptr_001_ = (pvt_cstr_metadata_t*)pvt_cstr_malloc(tmp_size_001_);
}
pvt_cstr_assert(base_ptr_001_ != NULL, "allocation failed!!");
base_ptr_001_->is_malloced = true;
base_ptr_001_->capacity = (size_t)(cap_001_);
if(!ptr) base_ptr_001_->size = 0;
return (cstring)(base_ptr_001_ + 1);
}
/*
* Functions to multiple args Constructors
* ---------------------------------------
*/
CSTR_NODISCARD __attribute__((malloc))
cstring __cstr__unused
intl_init_cpy_ch(const size_t cnt, const char ch)
{
cstring str_ = NULL;
if(!cnt){
str_ = pvt_buf_grow_(NULL, CSTRING_DEFAULT_CAP);
str_[0] = (char)0;
pvt_set_total_size_(str_, 0);
}
intl_assign_cnt_ch(&str_, cnt, ch);
return str_;
}
CSTR_NODISCARD __attribute__((malloc))
cstring __cstr__unused
intl_copy_constructor(const_cstring other)
{
size_t size_ = pvt_cstr_strlen(other);
cstring str_ = pvt_buf_grow_(NULL, size_ + 1);
pvt_copy_(str_, other, size_);
return str_;
}
CSTR_NODISCARD __attribute__((malloc))
cstring __cstr__unused
intl_init_cpy_str_w_iter(cstr_iterator begin,
cstr_iterator end)
{
pvt_cstr_assert((begin.it < end.it),
"Unsupported arguments passed for Constructor");
size_t size_ = cstr_distance(begin, end);
cstring str_ = pvt_buf_grow_(NULL, size_ + 1);
pvt_copy_(str_, begin.it, size_);
return str_;
}
CSTR_NODISCARD __attribute__((malloc))
cstring __cstr__unused
intl_init_cpy_str_w_off(const_cstring other,
const size_t start, const size_t offset)
{
size_t size_ = pvt_cstr_strlen(other),
count_ = CSTRMIN(offset, size_ - start);
pvt_cstr_assert(other != NULL && (start < size_),
"Unsupported arguments passed for Constructor");
cstring str_ = pvt_buf_grow_(NULL, count_ + 1);
pvt_copy_(str_, other + start, count_);
return str_;
}
right now a lot of the intl_* implementation functions are static inline in the header which I know causes code bloat per translation unit. Is moving them to .c with just declarations in the header the right fixIs it a bloat?
Also I have a helper functions for find in the library and i thought of the find function to accept a strategy parameter at the end so the user can choose the search algorithm in the macro itself, instead of having separate functions for each one. But the problem is implementation. Does anyone have any suggestions?
below is the macro for cstr_find:
/**
* Generic replace function for cstring.
*
* cstr_find(str, ch) -> find first character ch in str
* cstr_find(str, other) -> find first substring equal to other in str
* cstr_find(str, ch, pos) -> find first character ch in (str + pos)
* cstr_find(str, other, pos) -> find first substring equal to other in (str + pos)
* cstr_find(str, other, pos, count) -> find first substring equal to other in str[pos, pos+count)
*
* returns `size_t`
*/
#define cstr_find(...) \
__cstr_find_chooser(__VA_ARGS__)(__VA_ARGS__)
/*
* FIND
* ---------
*/
#define __cstr_find_get_macro(_1,_2,_3,_4,NAME,...) NAME
#define __cstr_find_chooser(...) \
__cstr_find_get_macro(__VA_ARGS__, \
intl_find_str_range, \
__cstr_find_3, \
__cstr_find_2)
#define __cstr_find_2(str, _2) \
_Generic( (_2), \
PVT_GENERIC_STRING_TYPES(intl_find_str), \
PVT_GENERIC_CHAR_TYPES(intl_find_ch), \
default : dummy_func \
)(str, _2)
#define __cstr_find_3(str, _2, pos) \
_Generic( (_2), \
PVT_GENERIC_STRING_TYPES(intl_find_str_offset), \
PVT_GENERIC_CHAR_TYPES(intl_find_ch_offset), \
default : dummy_func \
)(str, _2, pos)
Any obvious bugs or safety issues I might have missed.