#include #include #include #include struct string { usize len, cap; struct str_meta { enum str_ownership owned; union _str_meta_inner { // `STRF_OWNED` struct str_meta_owned { _Atomic usize refs; // Number of derrivations from this void (*m_free)(void*); // How to free (when `_MALLOC`). _Atomic bool orphan; // If a `derive` references this parent somehow, but this string has been `str_free()`d. Tell the derived string that sets the `refs` to 0 to free it. } owned; // `STRF_DERIVED` struct str_meta_derived { // String this is derived from, can be either owned or derived. struct string* _shared parent; // `str`'s offset from `parent->str`. usize offset; } derived; } _inner; } meta; // The start of this string slice. Regardless of slicing char* str; }; //TODO: We can use offsetof() to reverse the result of `str_new()` from char* (str field) to str_t*. inline static isize _str_wrap_index(usize _max, isize wr) { isize max = (isize)_max; ifU(max<0) FATAL("max too large, must be <= isize::max (%lu)", SIZE_MAX>>1); else if(!max) return 0; while(wr<0) wr = max + wr; wr = wr >=max ? wr % max : wr; debug_assert(wr>0); return wr; } // Create a cstring (nul-terminated) from `from`. This string is statically allocated and should not be modified. // `sl` - Max size of `from` buffer. If 0, then just free the allocated buffer and return empty string; // `from` - Non-nul-terminated string buffer to copy from. // `len` - A pointer to a wrappable index for `from`. If `len` is negative, then offset from `sl`, if it's positive, offset from 0; if it's NULL, use `sl` itself. It is wrapped around `sl` in the case that the computed offset is >= sl. // // # Notes // Don't `free()` this returned pointer, this function handles that on call and on program exit. static const char* _str_raw_to_cstrsa(usize sl, const char from[restrict sl], isize*pINOUT len) { static _Thread_local char* _ma_buf = NULL; if(!sl) { free(_ma_buf); //free(NULL) is allowed. _ma_buf=NULL; return ""; } _callcv(exit) void _cleanup() { free(_ma_buf); _ma_buf = NULL; } isize fp = (isize) (sl & (SIZE_MAX >> 1)); if(!len) len = &fp; TRACE("copying wrapped %ld bytes from buffer %p[%lu]", *len, from, sl); usize rl = (usize) (*len = _str_wrap_index(sl, *len)); if(_ma_buf) _ma_buf = realloc(_ma_buf, rl+1); else _ma_buf = malloc(rl+1); memmove(_ma_buf, from, rl); _ma_buf[rl] = 0; return (const char*)_ma_buf; } inline static bool _str_meta_is_owned(enum str_ownership owned) { return AS(owned, u32) & AS(STRF_OWNED, u32); } // Returns true for owned, false for derived inline static bool _str_meta_parts_from(enum str_ownership o, union _str_meta_inner *pIN inner, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive) { if(_str_meta_is_owned(o)) return (PTR_ASSIGN(own, &inner->owned), true); else return (PTR_ASSIGN(derive, &inner->derived), false); } inline static bool _str_is_owned(const str_t*pIN str) { return _str_meta_is_owned(str->meta.owned); } inline static bool _str_is_owned_parts(str_t*pIN str, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive) { return _str_meta_parts_from(str->meta.owned, &str->meta._inner, own, derive); } static union _str_meta_inner _str_default_meta(enum str_ownership oship, bool *pOUT owned) { union _str_meta_inner output; struct str_meta_owned* o_own = NULL; struct str_meta_derived* o_der = NULL; // PTR_ASSIGNx always evaluates both args, so this is fine PTR_ASSIGNx(owned, _str_meta_parts_from(oship, &output, &o_own, &o_der) ); switch(oship) { case STR_OWNED_STACK: case STR_OWNED_STATIC: debug_assert(o_own); *o_own = (struct str_meta_owned){ .refs = 0, .m_free = NULL, .orphan = false, }; break; case STR_OWNED_MALLOC: debug_assert(o_own); *o_own = (struct str_meta_owned){ .refs = 0, .m_free = &free, .orphan = false, }; break; case STR_DERIVED_STATIC: case STR_DERIVED_STACK: case STR_DERIVED_MALLOC: debug_assert(o_der); *o_der = (struct str_meta_derived){ .parent = NULL, .offset = 0, }; break; default: ERROR("Cannot assign metadata from ownership type 0x%x, returning 0'd metadata", AS(oship, u32)); memset(&output, 0, sizeof(output)); } return output; } /// Allocates a bare new str_t. The metadata will be incomplete for `derived` string slices, and `str` will be `NULL`. For `owned` strings, `str` will be `calloc()`ed to `cap+1`. static str_t _str_alloc_bare(usize cap, enum str_ownership owned) { bool is_owned; let meta_inner = _str_default_meta(owned, &is_owned); struct str_meta meta = { .owned = owned, ._inner = meta_inner, }; str_t str = { .len = 0, .cap = cap, .meta = meta, }; str.str = is_owned ? calloc(cap+1, 1) : NULL; return str; } static void _str_force_free(struct str_meta_owned*pIN o_own, void* ptr) { (o_own->m_free ?: &free)(ptr); } void str_free(str_t* restrict strp) { str_t str = unbox_value(strp); struct str_meta_owned* o_own = NULL; struct str_meta_derived* o_der = NULL; if(_str_is_owned_parts(&str, &o_own, &o_der)) { debug_assert(o_own); if(o_own->refs > 0) o_own->orphan = true; else if(str.meta.owned == STR_OWNED_MALLOC && str.str) _str_force_free(o_own, str.str); } else { debug_assert(o_der); TODO(" \ //TODO: Find top-level parent (traverse list of potentially derived `o_der->parent`s. \ //TODO: While doing so, compute the full offset that this slice has from the top owned parent. \ //TODO: Then, decrement its `refs` \ //TODO: Then, if it is marked as `orphan`, and we have decremented its `refs` to 0, free its `ptr`. \ "); } }