diff --git a/misc/nstr/README.md b/misc/nstr/README.md new file mode 100644 index 0000000..04b7d84 --- /dev/null +++ b/misc/nstr/README.md @@ -0,0 +1,25 @@ +# nstr +Of course yet another single-header string library for C. + +This library provides a simple `Str` type, supporting dynamic extending or fixed +static buffers. + +## Usage +In a single C file in your project do: +```c +#define NSTR_IMPLEMENTATION /* this is to enable the implementations */ +#include "nstr.h" +``` + +Then `nstr.h` can be freely included in any other file, which will only include the +signatures, macros and types. + +Functions and macros of this library are prefixed with `nstr_` (because `str*` is reserved in C). + +## Documentation +See the header file, the documentation are the comments. + +TODO some examples, for now the `nstr_test.c` serves as an example. + +## License +This library is under the Unlicense. diff --git a/misc/nstr/nstr.h b/misc/nstr/nstr.h new file mode 100644 index 0000000..95c1abd --- /dev/null +++ b/misc/nstr/nstr.h @@ -0,0 +1,211 @@ +#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 diff --git a/misc/nstr/nstr_test b/misc/nstr/nstr_test new file mode 100755 index 0000000..dbc938d Binary files /dev/null and b/misc/nstr/nstr_test differ diff --git a/misc/nstr/nstr_test.c b/misc/nstr/nstr_test.c new file mode 100644 index 0000000..23fc3b0 --- /dev/null +++ b/misc/nstr/nstr_test.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#define NSTR_IMPLEMENTATION +#define NSTR_LEN_TYPE size_t +#include "nstr.h" + +#define tmsg(s) fputs(s "... ", stderr) +#define tpass() fputs("passed\n", stderr) + +int +main(void) +{ + { + tmsg("String heap allocation with copy"); + + const char *cs1 = "hello woooooooooooooooorld aaaaaa!!!!aaaaaaaaaaaaaaaaaa"; + size_t cs1len = strlen(cs1); + Str s1 = nstr_new(cs1, cs1len); + s1.len = cs1len; + + assert(!nstr_is_null(s1)); /* ensure allocation success */ + assert(s1.s[s1.len] == '\0'); + assert(s1.len == cs1len); + assert(memcmp(s1.s, cs1, s1.len) == 0); + nstr_free(s1); + + tpass(); + } + { + tmsg("nstr_afmt"); + + Str s1 = nstr_afmt("%s/file/path/to/%s_%d", "/var", "log", 3129); + Str s2 = nstr_from_c("/var/file/path/to/log_3129"); + assert(nstr_equal(s1, s2)); + nstr_free(s1); + + tpass(); + } + { + tmsg("nstr_fmt"); + + Str buf = { .s = (char [128]){0}, .cap = 128 }; + Str s1 = nstr_fmt(buf, "%s/file/path/to/%s_%d", "/var", "log", 3129); + Str s2 = nstr_from_c("/var/file/path/to/log_3129"); + assert(nstr_equal(s1, s2)); + + tpass(); + } + { + tmsg("nstr_append with truncation"); + + Str s1 = { .s = (char [12]){0}, .cap = 12 }; + Str s2 = nstr_from_c("abcde"); + + s1 = nstr_append(s1, s2, NSTR_TRUNCATE); + assert(nstr_equal(s1, s2)); + s1 = nstr_append(s1, s2, NSTR_TRUNCATE); + assert(nstr_equal(s1, nstr_from_c("abcdeabcde"))); + s1 = nstr_append(s1, s2, NSTR_TRUNCATE); + assert(nstr_equal(s1, nstr_from_c("abcdeabcdea"))); + + tpass(); + } + { + tmsg("nstr_append with resizing"); + + Str s1 = nstr_new(NULL, 8); + s1 = nstr_append(s1, nstr_from_c("ABCDEFGHIJ"), NSTR_RESIZE); + s1 = nstr_append(s1, nstr_from_c("KLMNOP"), NSTR_RESIZE); + s1 = nstr_append(s1, nstr_from_c("QRSTUVWXYZ"), NSTR_RESIZE); + assert(nstr_equal(s1, nstr_from_c("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))); + nstr_free(s1); + + tpass(); + } + + return 0; +}