add nstr
This commit is contained in:
parent
8ae8a3f151
commit
7a3fedfea9
4 changed files with 317 additions and 0 deletions
25
misc/nstr/README.md
Normal file
25
misc/nstr/README.md
Normal 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
211
misc/nstr/nstr.h
Normal 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
BIN
misc/nstr/nstr_test
Executable file
Binary file not shown.
81
misc/nstr/nstr_test.c
Normal file
81
misc/nstr/nstr_test.c
Normal 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;
|
||||
}
|
Loading…
Add table
Reference in a new issue