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