From 6dba1922c1fa00765ca6cdec264c53939364cda7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Nov 2021 19:51:47 +0100 Subject: [PATCH] Add string buffer util This will help to build strings incrementally. --- app/meson.build | 5 +++ app/src/util/strbuf.c | 87 +++++++++++++++++++++++++++++++++++++++++ app/src/util/strbuf.h | 73 ++++++++++++++++++++++++++++++++++ app/tests/test_strbuf.c | 47 ++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 app/src/util/strbuf.c create mode 100644 app/src/util/strbuf.h create mode 100644 app/tests/test_strbuf.c diff --git a/app/meson.build b/app/meson.build index 2ced151b..f08d4cab 100644 --- a/app/meson.build +++ b/app/meson.build @@ -27,6 +27,7 @@ src = [ 'src/util/log.c', 'src/util/net.c', 'src/util/process.c', + 'src/util/strbuf.c', 'src/util/str_util.c', 'src/util/thread.c', 'src/util/tick.c', @@ -204,6 +205,10 @@ if get_option('buildtype') == 'debug' ['test_queue', [ 'tests/test_queue.c', ]], + ['test_strbuf', [ + 'tests/test_strbuf.c', + 'src/util/strbuf.c', + ]], ['test_strutil', [ 'tests/test_strutil.c', 'src/util/str_util.c', diff --git a/app/src/util/strbuf.c b/app/src/util/strbuf.c new file mode 100644 index 00000000..b2b6f494 --- /dev/null +++ b/app/src/util/strbuf.c @@ -0,0 +1,87 @@ +#include "strbuf.h" + +#include +#include +#include + +#include + +bool +sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) { + buf->s = malloc(init_cap + 1); // +1 for '\0' + if (!buf->s) { + return false; + } + + buf->len = 0; + buf->cap = init_cap; + return true; +} + +static bool +sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) { + if (buf->len + len > buf->cap) { + size_t new_cap = buf->cap * 3 / 2 + len; + char *s = realloc(buf->s, new_cap + 1); // +1 for '\0' + if (!s) { + // Leave the old buf->s + return false; + } + buf->s = s; + buf->cap = new_cap; + } + return true; +} + +bool +sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) { + assert(s); + assert(*s); + assert(strlen(s) >= len); + if (!sc_strbuf_reserve(buf, len)) { + return false; + } + + memcpy(&buf->s[buf->len], s, len); + buf->len += len; + buf->s[buf->len] = '\0'; + + return true; +} + +bool +sc_strbuf_append_char(struct sc_strbuf *buf, const char c) { + if (!sc_strbuf_reserve(buf, 1)) { + return false; + } + + buf->s[buf->len] = c; + buf->len ++; + buf->s[buf->len] = '\0'; + + return true; +} + +bool +sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) { + if (!sc_strbuf_reserve(buf, n)) { + return false; + } + + memset(&buf->s[buf->len], c, n); + buf->len += n; + buf->s[buf->len] = '\0'; + + return true; +} + +void +sc_strbuf_shrink(struct sc_strbuf *buf) { + assert(buf->len <= buf->cap); + if (buf->len != buf->cap) { + char *s = realloc(buf->s, buf->len + 1); // +1 for '\0' + assert(s); // decreasing the size may not fail + buf->s = s; + buf->cap = buf->len; + } +} diff --git a/app/src/util/strbuf.h b/app/src/util/strbuf.h new file mode 100644 index 00000000..1878df2f --- /dev/null +++ b/app/src/util/strbuf.h @@ -0,0 +1,73 @@ +#ifndef SC_STRBUF_H +#define SC_STRBUF_H + +#include "common.h" + +#include +#include +#include + +struct sc_strbuf { + char *s; + size_t len; + size_t cap; +}; + +/** + * Initialize the string buffer + * + * `buf->s` must be manually freed by the caller. + */ +bool +sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap); + +/** + * Append a string + * + * Append `len` characters from `s` to the buffer. + */ +bool +sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len); + +/** + * Append a char + * + * Append a single character to the buffer. + */ +bool +sc_strbuf_append_char(struct sc_strbuf *buf, const char c); + +/** + * Append a char `n` times + * + * Append the same characters `n` times to the buffer. + */ +bool +sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n); + +/** + * Append a NUL-terminated string + */ +static inline bool +sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) { + return sc_strbuf_append(buf, s, strlen(s)); +} + +/** + * Append a static string + * + * Append a string whose size is known at compile time (for + * example a string literal). + */ +#define sc_strbuf_append_staticstr(BUF, S) \ + sc_strbuf_append(BUF, S, sizeof(S) - 1) + +/** + * Shrink the buffer capacity to its current length + * + * This resizes `buf->s` to fit the content. + */ +void +sc_strbuf_shrink(struct sc_strbuf *buf); + +#endif diff --git a/app/tests/test_strbuf.c b/app/tests/test_strbuf.c new file mode 100644 index 00000000..97417677 --- /dev/null +++ b/app/tests/test_strbuf.c @@ -0,0 +1,47 @@ +#include "common.h" + +#include +#include +#include + +#include "util/strbuf.h" + +static void test_strbuf_simple(void) { + struct sc_strbuf buf; + bool ok = sc_strbuf_init(&buf, 10); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "Hello"); + assert(ok); + + ok = sc_strbuf_append_char(&buf, ' '); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "world"); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "!\n"); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "This is a test"); + assert(ok); + + ok = sc_strbuf_append_n(&buf, '.', 3); + assert(ok); + + assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); + + sc_strbuf_shrink(&buf); + assert(buf.len == buf.cap); + assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); + + free(buf.s); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_strbuf_simple(); + return 0; +}