#ifndef _nstr_h_ #define _nstr_h_ #ifndef NSTR_BUF_TYPE # define NSTR_BUF_TYPE char #endif #ifndef NSTR_LEN_TYPE # include typedef ptrdiff_t isize; # define NSTR_LEN_TYPE isize #endif #ifndef NSTR_ALLOC # define NSTR_ALLOC calloc #endif #ifndef NSTR_FREE # define NSTR_FREE free #endif #ifndef NSTR_ASSERT # define NSTR_ASSERT assert #endif #ifndef NSTR_WITH_CAP # define NSTR_WITH_CAP 1 #endif typedef struct Str Str; typedef enum StrAppendMode StrAppendMode; struct Str { NSTR_BUF_TYPE *s; /* string data */ NSTR_LEN_TYPE len; /* length */ NSTR_LEN_TYPE cap; /* capacity for appending */ }; enum StrAppendMode { NSTR_TRUNCATE, /* Append as much bytes as it can fit without resizing. */ NSTR_RESIZE, /* Assumes that the `Str` is heap allocated. */ }; /* Creates a `Str` from a buffer of size `len`. Asserts that the buffer is null-terminated */ Str nstr_from_buf(NSTR_BUF_TYPE *buf, NSTR_LEN_TYPE len); /* "Converts" a `Str` into a C string. Since `Str` are meant to be * null-terminated already, no conversion is made, but ensures that the * null terminator is present. */ char * nstr_to_c(Str s); /* Returns `true` if both strings are equal. */ int nstr_equal(Str s1, Str s2); /* Heaps allocates a new `Str` of size `len` + 1 (extra for the null terminator), copying * the contents from `data` if it isn't null to the new string. */ Str nstr_new(const NSTR_BUF_TYPE *data, NSTR_LEN_TYPE cap); /* Deallocates a heap-allocated `Str`. This function exists only for simmetry, * plain `free` can be used as well. */ void nstr_free(Str s); /* Appends contents from the string `s2` to `s1` according to the append `mode`. * Both `s1` and `s2` backing buffers must not overlap (because memcmp is used * instead of memmove). */ Str nstr_append(Str s1, Str s2, StrAppendMode mode); /* Formats a `Str`, storing the formatted string in `buf`. Formatting * arguments are passed as a va_list in `args`. Assumes that there is enough * space for a null terminator. */ Str nstr_vfmt(Str buf, const char *fmt, va_list args); /* Formats a `Str`, storing the formatted string in `buf`. Assumes that there is enough * space for a null terminator. */ Str nstr_fmt(Str buf, const char *fmt, ...); /* Returns a formatted string (heap allocated) of the exact required size. * The caller takes ownership of the string data and is responsible of freeing * the allocated memory. Requires `vsnprintf`. */ Str nstr_afmt(const char *fmt, ...); #define nstr_from_c(s) nstr_from_buf(s, strlen(s)) #define nstr_is_empty(str) ((str).len == 0) #define nstr_is_null(str) ((str).s == NULL) #ifdef NSTR_IMPLEMENTATION #include int vsnprintf(char *, unsigned long, const char *, va_list); Str nstr_from_buf(NSTR_BUF_TYPE *buf, NSTR_LEN_TYPE len) { NSTR_ASSERT(buf[len] == '\0'); return (Str){ buf, len, len }; } char * nstr_to_c(Str s) { if (s.len == 0 || s.s == NULL) return NULL; NSTR_ASSERT(s.s[s.len] == '\0'); return (char *)s.s; } int nstr_equal(Str s1, Str s2) { /* because passing nil to mem* is UB even if size == 0... */ return (s1.len == s2.len) && (s1.len == 0 || memcmp(s1.s, s2.s, s1.len) == 0); } Str nstr_new(const NSTR_BUF_TYPE *data, NSTR_LEN_TYPE cap) { NSTR_ASSERT(cap >= 0); Str s; if ((s.s = NSTR_ALLOC(cap + 1, sizeof(*s.s))) == NULL) return (Str){0}; s.cap = cap; if (data != NULL) { memcpy(s.s, data, cap); s.s[cap + 1] = '\0'; /* ensure */ } return s; } void nstr_free(Str s) { NSTR_FREE(s.s); } Str nstr_append(Str s1, Str s2, StrAppendMode mode) { switch (mode) { case NSTR_TRUNCATE: if (s1.len + s2.len > s1.cap) s2.len = s1.cap - 1 - s1.len; break; case NSTR_RESIZE: if (s1.cap + s2.len > s1.cap) { Str stmp = nstr_new(s1.s, s1.cap + s2.len); if (nstr_is_null(stmp)) return (Str){0}; s1.s = stmp.s; s1.cap = stmp.cap; } break; } memcpy(s1.s + s1.len, s2.s, s2.len); s1.len += s2.len; s1.s[s1.len] = '\0'; return s1; } Str nstr_vfmt(Str buf, const char *fmt, va_list args) { buf.len = (NSTR_LEN_TYPE)vsnprintf((char *)buf.s, buf.cap + 1, fmt, args); return buf; } Str nstr_fmt(Str buf, const char *fmt, ...) { va_list args; va_start(args, fmt); buf = nstr_vfmt(buf, fmt, args); va_end(args); return buf; } Str nstr_afmt(const char *fmt, ...) { va_list args; Str buf; va_start(args, fmt); /* Calculate buffer size required to hold the formatted string */ int reqs = vsnprintf(NULL, 0, fmt, args); if (reqs < 0) return (Str){0}; va_start(args, fmt); buf = nstr_new(NULL, (NSTR_LEN_TYPE)reqs); va_end(args); buf = nstr_vfmt(buf, fmt, args); if (nstr_is_null(buf)) /* allocation failed */ return (Str){0}; return buf; } #endif #endif