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.
376 lines
7.2 KiB
C
376 lines
7.2 KiB
C
#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);
|
|
}
|