|
|
|
@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
|
|
#include <str.h>
|
|
|
|
|
|
|
|
|
|
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_ASSIGNv always evaluates both args, so this is fine
|
|
|
|
|
PTR_ASSIGNv(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`. \
|
|
|
|
|
");
|
|
|
|
|
}
|
|
|
|
|
}
|