From 23aba2a27f8761afa9a54383736f0a61b768b932 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Tue, 3 Feb 2026 14:34:58 +0000 Subject: [PATCH] core: add bstr data structure bstr is very similar to b_stringstream, with the key difference being it can be stack-allocated. this allows you to write a complex formatted string to a stack-allocated character buffer with no heap memory allocation required. bstr also supports automatically-managed dynamic string buffers for unbounded string construction. --- core/bstr.c | 375 ++++++++++++++++++++++++++++++++++ core/include/blue/core/bstr.h | 71 +++++++ 2 files changed, 446 insertions(+) create mode 100644 core/bstr.c create mode 100644 core/include/blue/core/bstr.h diff --git a/core/bstr.c b/core/bstr.c new file mode 100644 index 0000000..6a0dc8f --- /dev/null +++ b/core/bstr.c @@ -0,0 +1,375 @@ +#include "printf.h" + +#include +#include +#include +#include +#include +#include + +#define CHECK_FLAG(str, f) ((((str)->bstr_flags) & (f)) == (f)) +#define IS_DYNAMIC(p) (CHECK_FLAG(p, B_BSTR_F_ALLOC)) +/* number of bytes that bstr_buf is extended by when required */ +#define CAPACITY_STEP 32 + +void b_bstr_begin(struct b_bstr *str, char *buf, size_t max) +{ + memset(str, 0x0, sizeof *str); + + str->bstr_magic = B_BSTR_MAGIC; + str->bstr_buf = buf; + str->bstr_capacity = max; + str->bstr_len = 0; + str->bstr_flags = B_BSTR_F_NONE; +} + +void b_bstr_begin_dynamic(struct b_bstr *str) +{ + memset(str, 0x0, sizeof *str); + + str->bstr_magic = B_BSTR_MAGIC; + str->bstr_buf = NULL; + str->bstr_capacity = 0; + str->bstr_len = 0; + str->bstr_flags = B_BSTR_F_ALLOC; +} + +static char *truncate_buffer(char *s, size_t len) +{ + if (!s || !len) { + return NULL; + } + + char *final = realloc(s, len + 1); + if (!final) { + return s; + } + + final[len] = '\0'; + + return final; +} + +char *b_bstr_end(struct b_bstr *str) +{ + char *out = str->bstr_buf; + size_t len = str->bstr_len; + + if (IS_DYNAMIC(str) && str->bstr_capacity - 1 > str->bstr_len) { + /* bstr_buf is larger than required to contain the string. + * re-allocate it so it's only as large as necessary */ + out = truncate_buffer(out, len); + } + + if (str->bstr_istack) { + free(str->bstr_istack); + } + + memset(str, 0x0, sizeof *str); + + return out; +} + +enum b_status b_bstr_reserve(struct b_bstr *strv, size_t len) +{ + if (!IS_DYNAMIC(strv)) { + return B_SUCCESS; + } + + if (strv->bstr_capacity > 0 && strv->bstr_capacity - 1 >= len) { + return B_SUCCESS; + } + + char *new_buf = realloc(strv->bstr_buf, len + 1); + if (!new_buf) { + return B_ERR_NO_MEMORY; + } + + strv->bstr_buf = new_buf; + strv->bstr_capacity = len + 1; + strv->bstr_buf[strv->bstr_len] = '\0'; + + return B_SUCCESS; +} + +static int current_indent(struct b_bstr *str) +{ + if (!str->bstr_istack || !str->bstr_istack_size) { + return 0; + } + + return str->bstr_istack[str->bstr_istack_ptr]; +} + +static void __formatter_putchar(struct b_bstr *str, char c) +{ + if (str->bstr_capacity > 0 && str->bstr_len < str->bstr_capacity - 1) { + str->bstr_buf[str->bstr_len++] = c; + str->bstr_buf[str->bstr_len] = '\0'; + return; + } + + if (!CHECK_FLAG(str, B_BSTR_F_ALLOC)) { + return; + } + + size_t old_capacity = str->bstr_capacity; + size_t new_capacity = old_capacity + CAPACITY_STEP; + + char *new_buf = realloc(str->bstr_buf, new_capacity); + if (!new_buf) { + return; + } + + str->bstr_buf = new_buf; + str->bstr_capacity = new_capacity; + str->bstr_buf[str->bstr_len++] = c; + str->bstr_buf[str->bstr_len] = '\0'; +} + +static void formatter_putchar(struct b_bstr *f, char c) +{ + if (f->bstr_add_indent && c != '\n') { + int indent = current_indent(f); + for (int i = 0; i < indent; i++) { + __formatter_putchar(f, ' '); + __formatter_putchar(f, ' '); + } + + f->bstr_add_indent = 0; + } + + __formatter_putchar(f, c); + + if (c == '\n') { + f->bstr_add_indent = 1; + } +} + +static void bstr_fctprintf(char c, void *arg) +{ + struct b_bstr *str = arg; + formatter_putchar(str, c); +} + +static enum b_status bstr_putcs( + struct b_bstr *str, const char *s, size_t len, size_t *nr_written) +{ + for (size_t i = 0; i < len; i++) { + formatter_putchar(str, s[i]); + } + + if (nr_written) { + *nr_written = len; + } + + return B_SUCCESS; +} + +static enum b_status bstr_puts(struct b_bstr *str, const char *s, size_t *nr_written) +{ + size_t i; + for (i = 0; s[i]; i++) { + formatter_putchar(str, s[i]); + } + + if (nr_written) { + *nr_written = i; + } + + return B_SUCCESS; +} + +enum b_status b_bstr_push_indent(struct b_bstr *str, int indent) +{ + if (!str->bstr_istack) { + str->bstr_istack = calloc(4, sizeof(int)); + str->bstr_istack_size = 4; + str->bstr_istack_ptr = 0; + } + + if (str->bstr_istack_ptr + 1 > str->bstr_istack_size) { + int *buf = realloc( + str->bstr_istack, + (str->bstr_istack_size + 4) * sizeof(int)); + if (!buf) { + return B_ERR_NO_MEMORY; + } + + str->bstr_istack = buf; + str->bstr_istack_size += 4; + } + + int cur_indent = str->bstr_istack[str->bstr_istack_ptr]; + str->bstr_istack[++str->bstr_istack_ptr] = cur_indent + indent; + + return B_SUCCESS; +} + +enum b_status b_bstr_pop_indent(struct b_bstr *strv) +{ + if (strv->bstr_istack_ptr > 0) { + strv->bstr_istack_ptr--; + } + + return B_SUCCESS; +} + +enum b_status b_bstr_write_char(struct b_bstr *str, char c) +{ + formatter_putchar(str, c); + return B_SUCCESS; +} + +enum b_status b_bstr_write_chars( + struct b_bstr *str, const char *s, size_t len, size_t *nr_written) +{ + return bstr_putcs(str, s, len, nr_written); +} + +enum b_status b_bstr_write_cstr(struct b_bstr *str, const char *s, size_t *nr_written) +{ + return bstr_puts(str, s, nr_written); +} + +enum b_status b_bstr_write_cstr_list( + struct b_bstr *str, const char **strs, size_t *nr_written) +{ + size_t w = 0; + enum b_status status = B_SUCCESS; + + for (size_t i = 0; strs[i]; i++) { + size_t tmp = 0; + status = bstr_puts(str, strs[i], &tmp); + w += tmp; + + if (B_ERR(status)) { + break; + } + } + + if (nr_written) { + *nr_written = w; + } + + return status; +} + +enum b_status b_bstr_write_cstr_array( + struct b_bstr *str, const char **strs, size_t count, size_t *nr_written) +{ + enum b_status status = B_SUCCESS; + size_t w = 0; + + for (size_t i = 0; i < count; i++) { + if (!strs[i]) { + continue; + } + + size_t tmp = 0; + status = bstr_puts(str, strs[i], &tmp); + w += tmp; + + if (B_ERR(status)) { + break; + } + } + + if (nr_written) { + *nr_written = w; + } + + return status; +} + +enum b_status b_bstr_add_many(struct b_bstr *str, size_t *nr_written, ...) +{ + va_list arg; + va_start(arg, nr_written); + const char *s = NULL; + size_t w = 0; + enum b_status status = B_SUCCESS; + + while ((s = va_arg(arg, const char *))) { + size_t tmp = 0; + status = bstr_puts(str, s, &tmp); + w += tmp; + + if (B_ERR(status)) { + break; + } + } + + if (nr_written) { + *nr_written = w; + } + + return status; +} + +enum b_status b_bstr_write_rope( + b_bstr *strv, const struct b_rope *rope, size_t *nr_written) +{ + size_t start = strv->bstr_len; + enum b_status status = b_rope_to_bstr(rope, strv); + size_t end = strv->bstr_len; + + if (nr_written) { + *nr_written = end - start; + } + + return status; +} + +enum b_status b_bstr_write_fmt( + struct b_bstr *str, size_t *nr_written, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + enum b_status result = b_bstr_write_vfmt(str, nr_written, format, arg); + va_end(arg); + + return result; +} + +enum b_status b_bstr_write_vfmt( + struct b_bstr *str, size_t *nr_written, const char *format, va_list arg) +{ + size_t start = str->bstr_len; + z__b_fctprintf(bstr_fctprintf, str, format, arg); + size_t end = str->bstr_len; + + if (nr_written) { + *nr_written = end - start; + } + + /* TODO update z__b_fctprintf to support propagating error codes */ + return B_SUCCESS; +} + +char *b_bstr_rope(const struct b_rope *rope, size_t *nr_written) +{ + struct b_bstr str; + b_bstr_begin_dynamic(&str); + b_bstr_write_rope(&str, rope, nr_written); + + return b_bstr_end(&str); +} + +char *b_bstr_fmt(size_t *nr_written, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + char *s = b_bstr_vfmt(nr_written, format, arg); + va_end(arg); + return s; +} + +char *b_bstr_vfmt(size_t *nr_written, const char *format, va_list arg) +{ + struct b_bstr str; + b_bstr_begin_dynamic(&str); + b_bstr_write_vfmt(&str, nr_written, format, arg); + + return b_bstr_end(&str); +} diff --git a/core/include/blue/core/bstr.h b/core/include/blue/core/bstr.h new file mode 100644 index 0000000..c344723 --- /dev/null +++ b/core/include/blue/core/bstr.h @@ -0,0 +1,71 @@ +#ifndef BLUE_CORE_BSTR_H_ +#define BLUE_CORE_BSTR_H_ + +#include +#include +#include +#include + +#define B_BSTR_MAGIC 0x5005500550055005ULL + +struct b_rope; + +enum b_bstr_flags { + B_BSTR_F_NONE = 0x00u, + B_BSTR_F_ALLOC = 0x01u, +}; + +typedef struct b_bstr { + uint64_t bstr_magic; + enum b_bstr_flags bstr_flags; + char *bstr_buf; + /* total number of characters in bstr_buf, not including null terminator */ + size_t bstr_len; + /* number of bytes allocated for bstr_buf (includes space for the null + * terminator) */ + size_t bstr_capacity; + int *bstr_istack; + int bstr_add_indent; + size_t bstr_istack_ptr, bstr_istack_size; +} b_bstr; + +BLUE_API void b_bstr_begin(b_bstr *strv, char *buf, size_t max); +BLUE_API void b_bstr_begin_dynamic(b_bstr *strv); +BLUE_API char *b_bstr_end(b_bstr *strv); +BLUE_API b_status b_bstr_reserve(b_bstr *strv, size_t len); + +static inline size_t b_bstr_get_size(const b_bstr *str) +{ + return str->bstr_len; +} + +static inline size_t b_bstr_get_capacity(const b_bstr *str) +{ + return str->bstr_capacity; +} + +BLUE_API b_status b_bstr_push_indent(b_bstr *strv, int indent); +BLUE_API b_status b_bstr_pop_indent(b_bstr *strv); + +BLUE_API b_status b_bstr_write_char(b_bstr *strv, char c); +BLUE_API b_status b_bstr_write_chars( + b_bstr *strv, const char *cs, size_t len, size_t *nr_written); +BLUE_API b_status b_bstr_write_cstr( + b_bstr *strv, const char *str, size_t *nr_written); +BLUE_API b_status b_bstr_write_cstr_list( + b_bstr *strv, const char **strs, size_t *nr_written); +BLUE_API b_status b_bstr_write_cstr_array( + b_bstr *strv, const char **strs, size_t count, size_t *nr_written); +BLUE_API b_status b_bstr_write_cstr_varg(b_bstr *strv, size_t *nr_written, ...); +BLUE_API b_status b_bstr_write_rope( + b_bstr *strv, const struct b_rope *rope, size_t *nr_written); +BLUE_API b_status b_bstr_write_fmt( + b_bstr *strv, size_t *nr_written, const char *format, ...); +BLUE_API b_status b_bstr_write_vfmt( + b_bstr *strv, size_t *nr_written, const char *format, va_list arg); + +BLUE_API char *b_bstr_rope(const struct b_rope *rope, size_t *nr_written); +BLUE_API char *b_bstr_fmt(size_t *nr_written, const char *format, ...); +BLUE_API char *b_bstr_vfmt(size_t *nr_written, const char *format, va_list arg); + +#endif