Browse Source
`sdd` was the first attempt at making one of these individually-allocated string management/utility things. I didn't end up liking the design, so I tried again from scratch, and called the new one `oso`. Let's see how that one turns out. The name might change again in the future, though I feel better about the design of this one.master

8 changed files with 462 additions and 350 deletions
@ -0,0 +1,232 @@ |
|||
#include "oso.h" |
|||
#include <stdint.h> |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) |
|||
#if __has_attribute(noinline) && __has_attribute(noclone) |
|||
#define OSO_NOINLINE __attribute__((noinline, noclone)) |
|||
#elif __has_attribute(noinline) |
|||
#define OSO_NOINLINE __attribute__((noinline)) |
|||
#endif |
|||
#elif defined(_MSC_VER) |
|||
#define OSO_NOINLINE __declspec(noinline) |
|||
#endif |
|||
#ifndef OSO_NOINLINE |
|||
#define OSO_NOINLINE |
|||
#endif |
|||
|
|||
#define OSO_INTERNAL OSO_NOINLINE static |
|||
#define OSO_HDR(s) ((oso_header *)s - 1) |
|||
#define OSO_CAP_MAX (SIZE_MAX - (sizeof(oso_header) + 1)) |
|||
|
|||
typedef struct oso { |
|||
size_t len; |
|||
size_t cap; |
|||
} oso_header; |
|||
|
|||
OSO_INTERNAL oso *oso_impl_reallochdr(oso_header *hdr, size_t new_cap) { |
|||
if (hdr) { |
|||
oso_header *new_hdr = realloc(hdr, sizeof(oso_header) + new_cap + 1); |
|||
if (!new_hdr) { |
|||
free(hdr); |
|||
return NULL; |
|||
} |
|||
new_hdr->cap = new_cap; |
|||
return new_hdr + 1; |
|||
} |
|||
hdr = malloc(sizeof(oso_header) + new_cap + 1); |
|||
if (!hdr) |
|||
return NULL; |
|||
hdr->len = 0; |
|||
hdr->cap = new_cap; |
|||
((char *)(hdr + 1))[0] = '\0'; |
|||
return hdr + 1; |
|||
} |
|||
OSO_INTERNAL oso *oso_impl_catvprintf(oso *s, char const *fmt, va_list ap) { |
|||
size_t old_len; |
|||
int required; |
|||
va_list cpy; |
|||
va_copy(cpy, ap); |
|||
required = vsnprintf(NULL, 0, fmt, cpy); |
|||
va_end(cpy); |
|||
osomakeroomfor(&s, (size_t)required); |
|||
if (!s) |
|||
return NULL; |
|||
old_len = OSO_HDR(s)->len; |
|||
vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap); |
|||
OSO_HDR(s)->len = old_len + (size_t)required; |
|||
return s; |
|||
} |
|||
|
|||
OSO_NOINLINE |
|||
void osoensurecap(oso **p, size_t new_cap) { |
|||
oso *s = *p; |
|||
if (new_cap > OSO_CAP_MAX) { |
|||
if (s) { |
|||
free(OSO_HDR(s)); |
|||
*p = NULL; |
|||
} |
|||
return; |
|||
} |
|||
oso_header *hdr = NULL; |
|||
if (s) { |
|||
hdr = OSO_HDR(s); |
|||
if (hdr->cap >= new_cap) |
|||
return; |
|||
} |
|||
*p = oso_impl_reallochdr(hdr, new_cap); |
|||
} |
|||
|
|||
OSO_NOINLINE |
|||
void osomakeroomfor(oso **p, size_t add_len) { |
|||
oso *s = *p; |
|||
oso_header *hdr = NULL; |
|||
size_t new_cap; |
|||
if (s) { |
|||
hdr = OSO_HDR(s); |
|||
size_t len = hdr->len, cap = hdr->cap; |
|||
if (len > OSO_CAP_MAX - add_len) { // overflow, goodnight
|
|||
free(hdr); |
|||
*p = NULL; |
|||
return; |
|||
} |
|||
new_cap = len + add_len; |
|||
if (cap >= new_cap) |
|||
return; |
|||
} else { |
|||
if (add_len > OSO_CAP_MAX) |
|||
return; |
|||
new_cap = add_len; |
|||
} |
|||
*p = oso_impl_reallochdr(hdr, new_cap); |
|||
} |
|||
|
|||
void osoput(oso **p, char const *restrict cstr) { |
|||
osoputlen(p, cstr, strlen(cstr)); |
|||
} |
|||
|
|||
OSO_NOINLINE |
|||
void osoputlen(oso **p, char const *restrict cstr, size_t len) { |
|||
oso *s = *p; |
|||
osoensurecap(&s, len); |
|||
if (s) { |
|||
OSO_HDR(s)->len = len; |
|||
memcpy((char *)s, cstr, len); |
|||
((char *)s)[len] = '\0'; |
|||
} |
|||
*p = s; |
|||
} |
|||
void osoputoso(oso **p, oso const *other) { |
|||
if (!other) |
|||
return; |
|||
osoputlen(p, (char const *)other, OSO_HDR(other)->len); |
|||
} |
|||
void osoputvprintf(oso **p, char const *fmt, va_list ap) { |
|||
*p = oso_impl_catvprintf(*p, fmt, ap); |
|||
} |
|||
void osoputprintf(oso **p, char const *fmt, ...) { |
|||
va_list ap; |
|||
va_start(ap, fmt); |
|||
*p = oso_impl_catvprintf(*p, fmt, ap); |
|||
va_end(ap); |
|||
} |
|||
void osocat(oso **p, char const *cstr) { osocatlen(p, cstr, strlen(cstr)); } |
|||
OSO_NOINLINE |
|||
void osocatlen(oso **p, char const *cstr, size_t len) { |
|||
oso *s = *p; |
|||
osomakeroomfor(&s, len); |
|||
if (s) { |
|||
oso_header *hdr = OSO_HDR(s); |
|||
size_t curr_len = hdr->len; |
|||
memcpy((char *)s + curr_len, cstr, len); |
|||
((char *)s)[curr_len + len] = '\0'; |
|||
hdr->len = curr_len + len; |
|||
} |
|||
*p = s; |
|||
} |
|||
void osocatoso(oso **p, oso const *other) { |
|||
if (!other) |
|||
return; |
|||
osocatlen(p, (char const *)other, OSO_HDR(other)->len); |
|||
} |
|||
void osocatvprintf(oso **p, char const *fmt, va_list ap) { |
|||
oso *s = *p; |
|||
if (s) { |
|||
OSO_HDR(s)->len = 0; |
|||
((char *)s)[0] = '\0'; |
|||
} |
|||
*p = oso_impl_catvprintf(s, fmt, ap); |
|||
} |
|||
void osocatprintf(oso **p, char const *fmt, ...) { |
|||
oso *s = *p; |
|||
if (s) { |
|||
OSO_HDR(s)->len = 0; |
|||
((char *)s)[0] = '\0'; |
|||
} |
|||
va_list ap; |
|||
va_start(ap, fmt); |
|||
*p = oso_impl_catvprintf(s, fmt, ap); |
|||
va_end(ap); |
|||
} |
|||
void osoclear(oso **p) { |
|||
oso *s = *p; |
|||
if (!s) |
|||
return; |
|||
OSO_HDR(s)->len = 0; |
|||
((char *)s)[0] = '\0'; |
|||
} |
|||
void osofree(oso *s) { |
|||
if (s) |
|||
free(OSO_HDR(s)); |
|||
} |
|||
void osowipe(oso **p) { |
|||
osofree(*p); |
|||
*p = NULL; |
|||
} |
|||
void ososwap(oso const **a, oso const **b) { |
|||
oso const *tmp = *a; |
|||
*a = *b; |
|||
*b = tmp; |
|||
} |
|||
size_t osolen(oso const *s) { return s ? OSO_HDR(s)->len : 0; } |
|||
size_t osocap(oso const *s) { return s ? OSO_HDR(s)->cap : 0; } |
|||
void osolencap(oso const *s, size_t *out_len, size_t *out_cap) { |
|||
if (!s) { |
|||
*out_len = 0; |
|||
*out_cap = 0; |
|||
return; |
|||
} |
|||
oso_header *hdr = OSO_HDR(s); |
|||
*out_len = hdr->len; |
|||
*out_cap = hdr->cap; |
|||
} |
|||
size_t osoavail(oso const *s) { |
|||
if (!s) |
|||
return 0; |
|||
oso_header *h = OSO_HDR(s); |
|||
return h->cap - h->len; |
|||
} |
|||
|
|||
void osotrim(oso *restrict s, char const *restrict cut_set) { |
|||
if (!s) |
|||
return; |
|||
char *str, *start, *end, *start_pos, *end_pos; |
|||
start_pos = start = str = (char *)s; |
|||
end_pos = end = str + OSO_HDR(s)->len - 1; |
|||
while (start_pos <= end && strchr(cut_set, *start_pos)) |
|||
start_pos++; |
|||
while (end_pos > start_pos && strchr(cut_set, *end_pos)) |
|||
end_pos--; |
|||
size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1); |
|||
OSO_HDR(s)->len = len; |
|||
if (str != start_pos) |
|||
memmove(str, start_pos, len); |
|||
str[len] = '\0'; |
|||
} |
|||
|
|||
#undef OSO_HDR |
|||
#undef OSO_NOINLINE |
|||
#undef OSO_CAP_MAX |
|||
#undef OSO_INTERNAL |
@ -0,0 +1,182 @@ |
|||
#pragma once |
|||
// Heap-allocated string handling.
|
|||
// Inspired by antirez's sds and gingerBill's gb_string.h.
|
|||
//
|
|||
// "I need null-terminated strings to interact with libc and/or POSIX, and my
|
|||
// software isn't serious enough to warrant using arena or page allocation
|
|||
// strategies. Manually fussing with null-terminated strings with libc sucks,
|
|||
// even though we're allocating everything individually on the heap! Why can't
|
|||
// we at least get a nicer interface for the trade-off we've already made?"
|
|||
//
|
|||
// EXAMPLE
|
|||
// ---------
|
|||
// oso *mystring = NULL;
|
|||
// osoput(&mystring, "Hello World");
|
|||
// printf((char *)mystring);
|
|||
// osoput(&mystring, "How about some pancakes?");
|
|||
// printf((char *)mystring);
|
|||
// osocat(&mstring, " Sure!");
|
|||
// printf((char *)mystring);
|
|||
// osofree(mystring);
|
|||
//
|
|||
// > Hello World!
|
|||
// > How about some pancakes?
|
|||
// > How about some pancakes? Sure!
|
|||
//
|
|||
// RULES
|
|||
// -------
|
|||
// 1. `oso *` can always be cast to `char *`, but it's your job to check if
|
|||
// it's null before passing it to on something that doesn't tolerate null
|
|||
// pointers.
|
|||
//
|
|||
// 2. The functions defined in this header tolerate null pointers like this:
|
|||
//
|
|||
// `oso *` -> OK to be null.
|
|||
// `char const *` -> Must not be null.
|
|||
// `oso **` -> Must not be null, but the `oso *` pointed to
|
|||
// can be null. The pointed-to `oso *` may be
|
|||
// modified during the call.
|
|||
//
|
|||
// 3. `oso *` and `char const *` as arguments to the functions here must not
|
|||
// overlap in memory. During the call, the buffer pointed to by a `oso *`
|
|||
// might need to be reallocated in memory to make room for the `char const
|
|||
// *` contents, and if the `char const *` contents end up being moved by
|
|||
// that operation, the pointer will no longer be pointing at valid memory.
|
|||
// (This also applies to functions which accept two `oso *` as inputs.)
|
|||
//
|
|||
// Parameters with the type `oso *` are safe to pass as a null pointer. But
|
|||
// `oso **` parameters shouldn't be null. The `oso *` pointed to by the `oso
|
|||
// **` can be null, though. In other words, you need to do `&mystring` to pass
|
|||
// a `oso **` argument.
|
|||
//
|
|||
// During the function call, if the `oso *` pointed to by the `oso **` needs to
|
|||
// become non-null, or if the existing buffer needs to be moved to grow larger,
|
|||
// the `oso *` will be set to a new value.
|
|||
//
|
|||
// oso *mystring = NULL;
|
|||
// osolen(mystring); // Gives 0
|
|||
// osocat(&mystring, "waffles");
|
|||
// osolen(mystring); // Gives 7
|
|||
// osofree(mystring);
|
|||
//
|
|||
// NAMES
|
|||
// -------
|
|||
// osoput______ -> Copy a string into an oso string.
|
|||
// osocat______ -> Append a string onto the end of an oso string.
|
|||
// ______len -> Do it with an explicit length argument, so the C-string
|
|||
// doesn't have to be '\0'-terminated.
|
|||
// ______oso -> Do it with a second oso string.
|
|||
// ______printf -> Do it by using printf.
|
|||
//
|
|||
// ALLOC FAILURE
|
|||
// ---------------
|
|||
// If an allocation fails (including failing to reallocate) the `oso *` will be
|
|||
// set to null. If you decide to handle memory allocation failures, you'll need
|
|||
// to check for that.
|
|||
//
|
|||
// In the oso code, if a reallocation of an existing buffer fails (`realloc()`
|
|||
// returns null) then the old, still-valid buffer is immediately freed.
|
|||
// Therefore, in an out-of-memory situation, it's possible that you will *lose*
|
|||
// your strings without getting a chance to do something with the old buffers.
|
|||
//
|
|||
// Variations of the oso functions may be added in the future, with a _c suffix
|
|||
// or something, which preserve the old buffer and return an error code in the
|
|||
// event of a reallocation failure. I'm not sure how important this is, since
|
|||
// most existing libc-based software doesn't handle out-of-memory conditions
|
|||
// for small strings without imploding.
|
|||
//
|
|||
// (sds, for example, will lose your string *and* leak the old buffer if a
|
|||
// reallocation fails.)
|
|||
|
|||
#include <stdarg.h> |
|||
#include <stddef.h> |
|||
|
|||
#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) |
|||
#if __has_attribute(format) |
|||
#define OSO_PRINTF(...) __attribute__((format(printf, __VA_ARGS__))) |
|||
#endif |
|||
#if __has_attribute(nonnull) |
|||
#define OSO_NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) |
|||
#endif |
|||
#endif |
|||
#ifndef OSO_PRINTF |
|||
#define OSO_PRINTF(...) |
|||
#endif |
|||
#ifndef OSO_NONNULL |
|||
#define OSO_NONNULL(...) |
|||
#endif |
|||
|
|||
typedef struct oso oso; |
|||
|
|||
#define osoc(s) ((char const *)s) |
|||
|
|||
void osoput(oso **p, char const *cstr) OSO_NONNULL(); |
|||
// ^- Copies the '\0'-terminated string into the `oso *` string located at
|
|||
// `*p`. If `*p` is null or there isn't enough capacity to hold `cstr`, it
|
|||
// will be reallocated. The pointer value at `*p` will be updated if
|
|||
// necessary. `*p` and `cstr` must not point to overlapping memory.
|
|||
void osoputlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); |
|||
// ^- Same as above, but uses an additional parameter that specifies the length
|
|||
// of `cstr, and `cstr` does not have to be '\0'-terminated.
|
|||
// `*p` and `cstr` must not point to overlapping memory.
|
|||
void osoputoso(oso **p, oso const *other) OSO_NONNULL(1); |
|||
// ^- Same as above, but using another `oso`. `*p` and `other` must not point
|
|||
// to overlapping memory.
|
|||
void osoputvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) |
|||
OSO_PRINTF(2, 0); |
|||
void osoputprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) |
|||
OSO_PRINTF(2, 3); |
|||
// ^- Same as above, but do it by using printf.
|
|||
|
|||
void osocat(oso **p, char const *cstr) OSO_NONNULL(); |
|||
void osocatlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); |
|||
void osocatoso(oso **p, oso const *other) OSO_NONNULL(1); |
|||
void osocatvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) |
|||
OSO_PRINTF(2, 0); |
|||
void osocatprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) |
|||
OSO_PRINTF(2, 3); |
|||
// ^- Append string to oso string. Same rules as `osoput` family.
|
|||
|
|||
void osoensurecap(oso **p, size_t cap) OSO_NONNULL(); |
|||
// ^- Ensure that s has at least `cap` memory allocated for it. This does not
|
|||
// care about the strlen of the characters or the prefixed length count --
|
|||
// only the backing memory allocation.
|
|||
void osomakeroomfor(oso **p, size_t len) OSO_NONNULL(); |
|||
// ^- Ensure that s has enough allocated space after the '\0'-terminnation
|
|||
// character to hold an additional add_len characters. It does not adjust
|
|||
// the `length` number value, only the capacity, if necessary.
|
|||
//
|
|||
// Both `osoensurecap()` and `osomakeroomfor()` can be used to avoid making
|
|||
// multiple smaller reallactions as the string grows in the future. You can
|
|||
// also use them if you're going to modify the string buffer manually in
|
|||
// your own code, and need to create some space in the buffer.
|
|||
|
|||
void osoclear(oso **p) OSO_NONNULL(); |
|||
// ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place.
|
|||
void osofree(oso *s); |
|||
// ^- You know. And calling with null is allowed.
|
|||
void osowipe(oso **p) OSO_NONNULL(); |
|||
// ^- It's like `osofree()`, except you give it a ptr-to-ptr, and it also sets
|
|||
// `*p` to null for you when it's done freeing the memory.
|
|||
void ososwap(oso const **a, oso const **b) OSO_NONNULL(); |
|||
// ^- Swaps the two pointers. Yeah, that's all it does. Why? Because it's
|
|||
// common when dealing memory management for individually allocated strings
|
|||
// and changing between old and new string values.
|
|||
void osopokelen(oso *s, size_t len) OSO_NONNULL(); |
|||
// ^- Manually updates length field. Doesn't do anything else for you.
|
|||
|
|||
size_t osolen(oso const *s); |
|||
// ^- Bytes in use by the string (not including the '\0' character.)
|
|||
size_t osocap(oso const *s); |
|||
// ^- Bytes allocated on heap (not including the '\0' terminator.)
|
|||
void osolencap(oso const *s, size_t *out_len, size_t *out_cap) |
|||
OSO_NONNULL(2, 3); |
|||
// ^- Get both the len and the cap in one call.
|
|||
size_t osoavail(oso const *s); |
|||
// ^- osocap(s) - osolen(s)
|
|||
|
|||
void osotrim(oso *restrict s, char const *restrict cut_set) OSO_NONNULL(2); |
|||
// ^- Remove the characters in `cut_set` from the beginning and ending of `s`.
|
|||
|
|||
#undef OSO_PRINTF |
|||
#undef OSO_NONNULL |
@ -1,198 +0,0 @@ |
|||
// Derived from gingerBill's public domain gb_string.h file.
|
|||
#include "sdd.h" |
|||
#include <stdint.h> |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) |
|||
#if __has_attribute(noinline) && __has_attribute(noclone) |
|||
#define SDD_NOINLINE __attribute__((noinline, noclone)) |
|||
#elif __has_attribute(noinline) |
|||
#define SDD_NOINLINE __attribute__((noinline)) |
|||
#endif |
|||
#elif defined(_MSC_VER) |
|||
#define SDD_NOINLINE __declspec(noinline) |
|||
#endif |
|||
#ifndef SDD_NOINLINE |
|||
#define SDD_NOINLINE |
|||
#endif |
|||
|
|||
#define SDD_INTERNAL SDD_NOINLINE static |
|||
#define SDD_HDR(s) ((sdd_header *)s - 1) |
|||
#define SDD_CAP_MAX (SIZE_MAX - (sizeof(sdd_header) + 1)) |
|||
|
|||
typedef struct sdd { |
|||
size_t len; |
|||
size_t cap; |
|||
} sdd_header; |
|||
|
|||
SDD_INTERNAL sdd *sdd_impl_new(char const *init, size_t len, size_t cap) { |
|||
if (cap > SDD_CAP_MAX) |
|||
return NULL; |
|||
sdd_header *header = (sdd *)malloc(sizeof(sdd) + cap + 1); |
|||
if (!header) |
|||
return NULL; |
|||
header->len = len; |
|||
header->cap = cap; |
|||
char *str = (char *)(header + 1); |
|||
if (init) |
|||
memcpy(str, init, len); |
|||
str[len] = '\0'; |
|||
return (sdd *)str; |
|||
} |
|||
SDD_INTERNAL sdd *sdd_impl_reallochdr(sdd_header *hdr, size_t new_cap) { |
|||
sdd_header *new_hdr = realloc(hdr, sizeof(sdd_header) + new_cap + 1); |
|||
if (!new_hdr) { |
|||
free(hdr); |
|||
return NULL; |
|||
} |
|||
new_hdr->cap = new_cap; |
|||
return new_hdr + 1; |
|||
} |
|||
SDD_INTERNAL sdd *sdd_impl_catvprintf(sdd *s, char const *fmt, va_list ap) { |
|||
size_t old_len; |
|||
int required; |
|||
va_list cpy; |
|||
va_copy(cpy, ap); |
|||
required = vsnprintf(NULL, 0, fmt, cpy); |
|||
va_end(cpy); |
|||
if (s) { |
|||
old_len = SDD_HDR(s)->len; |
|||
s = sdd_makeroomfor(s, (size_t)required); |
|||
} else { |
|||
old_len = 0; |
|||
s = sdd_newcap((size_t)required); |
|||
} |
|||
if (!s) |
|||
return NULL; |
|||
vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap); |
|||
SDD_HDR(s)->len = old_len + (size_t)required; |
|||
return s; |
|||
} |
|||
|
|||
sdd *sdd_new(char const *str) { |
|||
size_t len = strlen(str); |
|||
return sdd_impl_new(str, len, len); |
|||
} |
|||
sdd *sdd_newlen(char const *init, size_t len) { |
|||
return sdd_impl_new(init, len, len); |
|||
} |
|||
sdd *sdd_newcap(size_t cap) { return sdd_impl_new(NULL, 0, cap); } |
|||
sdd *sdd_dup(sdd const *str) { |
|||
size_t len = SDD_HDR(str)->len; |
|||
return sdd_impl_new((char const *)str, len, len); |
|||
} |
|||
sdd *sdd_newvprintf(char const *fmt, va_list ap) { |
|||
return sdd_impl_catvprintf(NULL, fmt, ap); |
|||
} |
|||
sdd *sdd_newprintf(char const *fmt, ...) { |
|||
sdd *s; |
|||
va_list ap; |
|||
va_start(ap, fmt); |
|||
s = sdd_impl_catvprintf(NULL, fmt, ap); |
|||
va_end(ap); |
|||
return s; |
|||
} |
|||
void sdd_free(sdd *s) { |
|||
if (!s) |
|||
return; |
|||
free(s - 1); |
|||
} |
|||
sdd *sdd_cpy(sdd *restrict s, char const *restrict cstr) { |
|||
return sdd_cpylen(s, cstr, strlen(cstr)); |
|||
} |
|||
SDD_NOINLINE |
|||
sdd *sdd_cpylen(sdd *restrict s, char const *restrict cstr, size_t len) { |
|||
s = sdd_ensurecap(s, len); |
|||
if (!s) |
|||
return NULL; |
|||
SDD_HDR(s)->len = len; |
|||
memcpy(s, cstr, len); |
|||
((char *)s)[len] = '\0'; |
|||
return s; |
|||
} |
|||
sdd *sdd_cpysdd(sdd *restrict s, sdd const *restrict other) { |
|||
return sdd_cpylen(s, (char const *)other, SDD_HDR(other)->len); |
|||
} |
|||
sdd *sdd_cat(sdd *restrict s, char const *restrict other) { |
|||
return sdd_catlen(s, other, strlen(other)); |
|||
} |
|||
SDD_NOINLINE |
|||
sdd *sdd_catlen(sdd *restrict s, char const *restrict other, size_t other_len) { |
|||
size_t curr_len = SDD_HDR(s)->len; |
|||
s = sdd_makeroomfor(s, other_len); |
|||
if (!s) |
|||
return NULL; |
|||
memcpy((char *)s + curr_len, other, other_len); |
|||
((char *)s)[curr_len + other_len] = '\0'; |
|||
SDD_HDR(s)->len = curr_len + other_len; |
|||
return s; |
|||
} |
|||
sdd *sdd_catsdd(sdd *restrict s, sdd const *restrict other) { |
|||
return sdd_catlen(s, (char const *)other, SDD_HDR(other)->len); |
|||
} |
|||
sdd *sdd_catvprintf(sdd *restrict s, char const *fmt, va_list ap) { |
|||
return sdd_impl_catvprintf(s, fmt, ap); |
|||
} |
|||
sdd *sdd_catprintf(sdd *restrict s, char const *fmt, ...) { |
|||
va_list ap; |
|||
va_start(ap, fmt); |
|||
s = sdd_impl_catvprintf(s, fmt, ap); |
|||
va_end(ap); |
|||
return s; |
|||
} |
|||
SDD_NOINLINE |
|||
sdd *sdd_ensurecap(sdd *s, size_t new_cap) { |
|||
sdd_header *hdr = SDD_HDR(s); |
|||
if (new_cap > SDD_CAP_MAX) { |
|||
free(hdr); |
|||
return NULL; |
|||
} |
|||
if (hdr->cap >= new_cap) |
|||
return s; |
|||
return sdd_impl_reallochdr(hdr, new_cap); |
|||
} |
|||
SDD_NOINLINE |
|||
sdd *sdd_makeroomfor(sdd *s, size_t add_len) { |
|||
sdd_header *hdr = SDD_HDR(s); |
|||
size_t len = hdr->len, cap = hdr->cap; |
|||
if (len > SDD_CAP_MAX - add_len) { // overflow, goodnight
|
|||
free(hdr); |
|||
return NULL; |
|||
} |
|||
size_t new_cap = len + add_len; |
|||
if (cap >= new_cap) |
|||
return s; |
|||
return sdd_impl_reallochdr(hdr, new_cap); |
|||
} |
|||
size_t sdd_len(sdd const *s) { return SDD_HDR(s)->len; } |
|||
size_t sdd_cap(sdd const *s) { return SDD_HDR(s)->cap; } |
|||
size_t sdd_avail(sdd const *s) { |
|||
sdd_header *h = SDD_HDR(s); |
|||
return h->cap - h->len; |
|||
} |
|||
void sdd_clear(sdd *s) { |
|||
SDD_HDR(s)->len = 0; |
|||
((char *)s)[0] = '\0'; |
|||
} |
|||
void sdd_pokelen(sdd *s, size_t len) { SDD_HDR(s)->len = len; } |
|||
void sdd_trim(sdd *restrict s, char const *restrict cut_set) { |
|||
char *str, *start, *end, *start_pos, *end_pos; |
|||
start_pos = start = str = (char *)s; |
|||
end_pos = end = str + SDD_HDR(s)->len - 1; |
|||
while (start_pos <= end && strchr(cut_set, *start_pos)) |
|||
start_pos++; |
|||
while (end_pos > start_pos && strchr(cut_set, *end_pos)) |
|||
end_pos--; |
|||
size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1); |
|||
SDD_HDR(s)->len = len; |
|||
if (str != start_pos) |
|||
memmove(str, start_pos, len); |
|||
str[len] = '\0'; |
|||
} |
|||
|
|||
#undef SDD_HDR |
|||
#undef SDD_NOINLINE |
|||
#undef SDD_CAP_MAX |
|||
#undef SDD_INTERNAL |
@ -1,104 +0,0 @@ |
|||
// Strings, Dynamic, Dumb
|
|||
#pragma once |
|||
#include <stdarg.h> |
|||
#include <stddef.h> |
|||
|
|||
#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) |
|||
#if __has_attribute(format) |
|||
#define SDD_PRINTF(n1, n2) __attribute__((format(printf, n1, n2))) |
|||
#endif |
|||
#if __has_attribute(nonnull) |
|||
#define SDD_NONNULL(...) __attribute__((nonnull __VA_ARGS__)) |
|||
#endif |
|||
#if __has_attribute(malloc) && __has_attribute(warn_unused_result) |
|||
#define SDD_ALLOC __attribute__((malloc, warn_unused_result)) |
|||
#elif __has_attribute(warn_unused_result) |
|||
#define SDD_ALLOC __attribute__((warn_unused_result)) |
|||
#endif |
|||
#if __has_attribute(warn_unused_result) |
|||
#define SDD_USED __attribute__((warn_unused_result)) |
|||
#endif |
|||
#endif |
|||
#ifndef SDD_PRINTF |
|||
#define SDD_PRINTF(n1, n2) |
|||
#endif |
|||
#ifndef SDD_NONNULL |
|||
#define SDD_NONNULL(...) |
|||
#endif |
|||
#ifndef SDD_ALLOC |
|||
#define SDD_ALLOC |
|||
#endif |
|||
#ifndef SDD_USED |
|||
#define SDD_USED |
|||
#endif |
|||
|
|||
typedef struct sdd sdd; |
|||
|
|||
#define sddc(s) ((char *)s) |
|||
#define sddcc(s) ((char const *)s) |
|||
|
|||
sdd *sdd_new(char const *s) SDD_NONNULL() SDD_ALLOC; |
|||
// ^- Create new with copy of '\0'-terminated cstring.
|
|||
sdd *sdd_newlen(char const *s, size_t len) SDD_NONNULL() SDD_ALLOC; |
|||
// ^- Same, but without calling strlen().
|
|||
// Resulting new string will be '\0'-terminated.
|
|||
sdd *sdd_newcap(size_t cap) SDD_ALLOC; |
|||
// ^- 'Raw' new with a specific capacity.
|
|||
// Length will be set to 0, and '\0' written at position 0.
|
|||
sdd *sdd_dup(sdd const *s) SDD_ALLOC; |
|||
// ^- Same as sdd_newlen(str, sdd_len(str))
|
|||
sdd *sdd_newvprintf(char const *fmt, va_list ap) SDD_ALLOC; |
|||
sdd *sdd_newprintf(char const *fmt, ...) SDD_PRINTF(1, 2) SDD_ALLOC; |
|||
// ^- Create new by using printf
|
|||
void sdd_free(sdd *s); |
|||
// ^- Calling with null is allowed.
|
|||
|
|||
sdd *sdd_cpy(sdd *restrict s, char const *restrict cstr) SDD_NONNULL() SDD_USED; |
|||
// ^- Set `s` to contain the contents of `cstr`. This is really more like
|
|||
// "change into" rather than "copy".
|
|||
sdd *sdd_cpylen(sdd *restrict s, char const *restrict cstr, size_t len) |
|||
SDD_NONNULL() SDD_USED; |
|||
sdd *sdd_cpysdd(sdd *restrict s, sdd const *restrict other); |
|||
|
|||
sdd *sdd_cat(sdd *restrict s, char const *restrict other) |
|||
SDD_NONNULL() SDD_USED; |
|||
// ^- Appends contents. The two strings must not overlap in memory.
|
|||
sdd *sdd_catlen(sdd *restrict s, char const *restrict other, size_t len) |
|||
SDD_NONNULL() SDD_USED; |
|||
sdd *sdd_catsdd(sdd *restrict s, sdd const *restrict other) |
|||
SDD_NONNULL() SDD_USED; |
|||
sdd *sdd_catvprintf(sdd *restrict s, char const *fmt, va_list ap) |
|||
SDD_NONNULL((1, 2)) SDD_USED; |
|||
sdd *sdd_catprintf(sdd *restrict s, char const *fmt, ...) SDD_NONNULL((1, 2)) |
|||
SDD_PRINTF(2, 3) SDD_USED; |
|||
// ^- Appends by using printf.
|
|||
|
|||
size_t sdd_len(sdd const *s) SDD_NONNULL(); |
|||
// ^- Bytes used by string (excluding '\0' terminator)
|
|||
size_t sdd_cap(sdd const *s) SDD_NONNULL(); |
|||
// ^- Bytes allocated on heap (excluding '\0' terminator)
|
|||
size_t sdd_avail(sdd const *s) SDD_NONNULL(); |
|||
// ^- sdd_cap(s) - sdd_len(s)
|
|||
|
|||
void sdd_clear(sdd *s) SDD_NONNULL(); |
|||
// ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place.
|
|||
void sdd_pokelen(sdd *s, size_t len) SDD_NONNULL(); |
|||
// ^- Manually update length field. Doesn't do anything else for you.
|
|||
|
|||
void sdd_trim(sdd *restrict s, char const *cut_set) SDD_NONNULL(); |
|||
// ^- Remove the characters in cut_set from the beginning and ending of s.
|
|||
sdd *sdd_ensurecap(sdd *s, size_t cap) SDD_NONNULL() SDD_USED; |
|||
// ^- Ensure that s has at least cap memory allocated for it. This does not
|
|||
// care about the strlen of the characters or the prefixed length count --
|
|||
// only the backing memory allocation.
|
|||
sdd *sdd_makeroomfor(sdd *s, size_t add_len) SDD_NONNULL() SDD_USED; |
|||
// ^- Ensure that s has enough allocated space after the valid,
|
|||
// null-terminated characters to hold an additional add_len characters. It
|
|||
// does not adjust the length, only the capacity, if necessary. Soon after
|
|||
// you call sdd_makeroomfor(), you probably will want to call sdd_pokelen(),
|
|||
// otherwise you're probably using it incorrectly.
|
|||
|
|||
#undef SDD_PRINTF |
|||
#undef SDD_NONNULL |
|||
#undef SDD_ALLOC |
|||
#undef SDD_USED |
Loading…
Reference in new issue