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:
375
core/bstr.c
Normal file
375
core/bstr.c
Normal 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);
|
||||||
|
}
|
||||||
71
core/include/blue/core/bstr.h
Normal file
71
core/include/blue/core/bstr.h
Normal 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
|
||||||
Reference in New Issue
Block a user