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.
This commit is contained in:
2026-02-03 14:34:58 +00:00
parent bdcd4163c7
commit 23aba2a27f
2 changed files with 446 additions and 0 deletions

375
core/bstr.c Normal file
View File

@@ -0,0 +1,375 @@
#include "printf.h"
#include <blue/core/bstr.h>
#include <blue/core/rope.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@@ -0,0 +1,71 @@
#ifndef BLUE_CORE_BSTR_H_
#define BLUE_CORE_BSTR_H_
#include <blue/core/misc.h>
#include <blue/core/status.h>
#include <stdarg.h>
#include <stddef.h>
#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