This commit is contained in:
tocariimaa 2025-04-05 19:31:25 -03:00
parent 8ae8a3f151
commit 7a3fedfea9
4 changed files with 317 additions and 0 deletions

25
misc/nstr/README.md Normal file
View file

@ -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.

211
misc/nstr/nstr.h Normal file
View file

@ -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 <stddef.h>
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 <stdarg.h>
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

BIN
misc/nstr/nstr_test Executable file

Binary file not shown.

81
misc/nstr/nstr_test.c Normal file
View file

@ -0,0 +1,81 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#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;
}