#include #include #include #define STRING_INLINE_CAPACITY 15 struct b_string_p { /* length of string, not including null-terminator */ unsigned int s_len; /* maximum length of string storable in the currently-allocated buffer, not including null terminator */ unsigned int s_max; union { char d_inline[STRING_INLINE_CAPACITY + 1]; char *d_external; } s_data; }; static void string_init(b_string *p, void *priv) { struct b_string_p *str = priv; str->s_len = 0; str->s_max = STRING_INLINE_CAPACITY; } static bool string_is_inline(const struct b_string_p *str) { /* strings cannot go below STRING_INLINE_CAPACITY capacity */ return str->s_max == STRING_INLINE_CAPACITY; } static char *string_ptr(struct b_string_p *str) { if (string_is_inline(str)) { return str->s_data.d_inline; } return str->s_data.d_external; } static int string_make_inline(struct b_string_p *str) { char *buffer = string_ptr(str); memcpy(str->s_data.d_inline, buffer, sizeof str->s_data.d_inline); str->s_data.d_inline[sizeof str->s_data.d_inline - 1] = '\0'; str->s_max = STRING_INLINE_CAPACITY; if (str->s_len >= str->s_max) { str->s_len = str->s_max; } free(buffer); return 0; } static int string_resize_large(struct b_string_p *str, size_t capacity) { char *buffer = string_ptr(str); char *new_buffer = realloc(buffer, capacity + 1); if (!new_buffer) { return -1; } str->s_max = capacity; str->s_data.d_external = new_buffer; return 0; } static int string_make_large(struct b_string_p *str, size_t capacity) { const char *old_buffer = string_ptr(str); char *buffer = malloc(capacity + 1); if (!buffer) { return -1; } memcpy(buffer, old_buffer, sizeof str->s_data.d_inline); buffer[str->s_len] = '\0'; str->s_max = capacity; str->s_data.d_external = buffer; return 0; } static int string_change_capacity(struct b_string_p *str, size_t capacity) { size_t old_capacity = str->s_max; if (capacity < STRING_INLINE_CAPACITY) { capacity = STRING_INLINE_CAPACITY; } bool was_inline = string_is_inline(str); bool is_now_inline = capacity == STRING_INLINE_CAPACITY; if (capacity == old_capacity) { /* this also handles the case where the old and new capacity both fit into the inline buffer. */ return 0; } if (!was_inline && is_now_inline) { /* string was large, is now small enough to fit inline. */ return string_make_inline(str); } if (!was_inline) { /* string was large, and is still large. */ return string_resize_large(str, capacity); } if (!is_now_inline) { /* string was inline, and now large enough to require a buffer. */ return string_make_large(str, capacity); } /* nothing to do */ return 0; } b_string *b_string_create_from_cstr(const char *s) { b_string *p = b_string_create(); if (!p) { return NULL; } if (!s) { return p; } struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); str->s_len = strlen(s); string_change_capacity(str, str->s_len); char *dest = string_ptr(str); memcpy(dest, s, str->s_len); dest[str->s_len] = 0; return p; } b_string *b_string_create_from_c(char c, size_t count) { b_string *p = b_string_create(); if (!p) { return NULL; } if (!count || !c) { return p; } struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); string_change_capacity(str, count); char *s = string_ptr(str); for (size_t i = 0; i < count; i++) { s[i] = c; } str->s_len = count; return p; } b_string *b_string_duplicate(const b_string *p) { b_string *q = b_string_create(); if (!q) { return NULL; } struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); struct b_string_p *new_str = b_object_get_private(q, struct b_string_p, B_TYPE_STRING); string_change_capacity(new_str, str->s_len); const char *src = string_ptr(str); char *dst = string_ptr(new_str); memcpy(dst, src, str->s_len); new_str->s_len = str->s_len; return q; } char *b_string_steal(b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); char *dest = NULL; char *src = string_ptr(str); if (string_is_inline(str)) { dest = b_strdup(src); src[0] = 0; } else { dest = src; str->s_data.d_external = NULL; str->s_max = STRING_INLINE_CAPACITY; } str->s_len = 0; return dest; } static b_status string_reserve(struct b_string_p *str, size_t capacity) { if (str->s_max >= capacity) { return B_SUCCESS; } int err = string_change_capacity(str, capacity); return err == 0 ? B_SUCCESS : B_ERR_NO_MEMORY; } b_status b_string_reserve(b_string *p, size_t capacity) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); return string_reserve(str, capacity); } b_status b_string_replace( b_string *p, size_t start, size_t length, const char *new_data) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); b_status status = B_SUCCESS; size_t new_data_len = strlen(new_data); if (start >= str->s_len) { return B_ERR_INVALID_ARGUMENT; } if (start + length >= str->s_len) { length = str->s_len - start; } size_t new_str_len = str->s_len - length + new_data_len; if (new_str_len > str->s_max) { status = string_reserve(str, new_str_len); } if (!B_OK(status)) { return status; } char *s = string_ptr(str); char *substitution_start = s + start; char *excess_src = s + start + length; size_t excess_length = str->s_len - start - length; char *excess_dest = substitution_start + new_data_len; memmove(excess_dest, excess_src, excess_length); memmove(substitution_start, new_data, new_data_len); s[new_str_len] = '\0'; str->s_len = new_str_len; return B_SUCCESS; } b_status b_string_replace_all(b_string *p, const char *new_data) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); size_t new_len = strlen(new_data); string_reserve(str, new_len); char *dest = string_ptr(str); memcpy(dest, new_data, new_len); dest[new_len] = '\0'; str->s_len = new_len; return B_SUCCESS; } b_status b_string_remove(b_string *p, size_t start, size_t length) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); b_status status = B_SUCCESS; if (start >= str->s_len) { return B_ERR_INVALID_ARGUMENT; } if (start + length >= str->s_len) { length = str->s_len - start; } size_t new_str_len = str->s_len - length; char *s = string_ptr(str); char *removal_start = s + start; char *excess_src = s + start + length; size_t excess_length = str->s_len - start - length; memmove(removal_start, excess_src, excess_length); s[new_str_len] = '\0'; str->s_len = new_str_len; return B_SUCCESS; } b_status b_string_transform(b_string *p, int (*transformer)(int)) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); char *s = string_ptr(str); for (size_t i = 0; i < str->s_len; i++) { int c = transformer(s[i]); if (c != 0) { s[i] = c; } } return B_SUCCESS; } static enum b_status stream_close(struct b_stream *stream) { b_string *str = stream->s_ptr; b_release(str); return B_SUCCESS; } static enum b_status stream_getc(struct b_stream *stream, int *out) { b_string *p = stream->s_ptr; struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (stream->s_cursor >= str->s_len) { return B_ERR_NO_DATA; } char *s = string_ptr(str); *out = s[stream->s_cursor]; stream->s_cursor++; return B_SUCCESS; } static enum b_status stream_read( struct b_stream *stream, unsigned char *buf, size_t count, size_t *nr_read) { b_string *p = stream->s_ptr; struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (stream->s_cursor >= str->s_len) { *nr_read = 0; return B_SUCCESS; } size_t available = str->s_len - stream->s_cursor; size_t to_read = b_min(size_t, count, available); char *s = string_ptr(str) + stream->s_cursor; memcpy(buf, s, to_read); *nr_read = to_read; return B_SUCCESS; } static enum b_status stream_write( struct b_stream *stream, const unsigned char *buf, size_t count, size_t *nr_written) { b_string *p = stream->s_ptr; struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); enum b_status status = B_SUCCESS; if (stream->s_cursor + count > str->s_max) { status = string_reserve(str, stream->s_cursor + count); } if (!B_OK(status)) { return status; } char *s = string_ptr(str) + stream->s_cursor; memcpy(s, buf, count); s[str->s_max] = '\0'; stream->s_cursor += count; str->s_len = b_max(size_t, str->s_len, stream->s_cursor + count); *nr_written = count; return B_SUCCESS; } static enum b_status stream_seek( struct b_stream *stream, long long offset, b_stream_seek_origin origin) { b_string *p = stream->s_ptr; struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); size_t abs_offset; switch (origin) { case B_STREAM_SEEK_START: abs_offset = offset; break; case B_STREAM_SEEK_CURRENT: abs_offset = stream->s_cursor + offset; break; case B_STREAM_SEEK_END: abs_offset = str->s_len + offset; break; default: return B_ERR_INVALID_ARGUMENT; } stream->s_cursor = abs_offset; return B_SUCCESS; } static enum b_status stream_reserve(struct b_stream *stream, size_t len) { b_string *p = stream->s_ptr; struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); size_t new_capacity = str->s_len + len; return string_reserve(str, new_capacity); } enum b_status b_string_open_stream(b_string *str, struct b_stream **out) { struct b_stream *stream = malloc(sizeof *stream); if (!stream) { return B_ERR_NO_MEMORY; } memset(stream, 0x0, sizeof *stream); stream->s_mode |= B_STREAM_READ | B_STREAM_WRITE; stream->s_ptr = b_retain(str); stream->s_close = stream_close; stream->s_getc = stream_getc; stream->s_read = stream_read; stream->s_write = stream_write; stream->s_seek = stream_seek; stream->s_reserve = stream_reserve; *out = stream; return B_SUCCESS; } static void string_insert( struct b_string_p *dest, const char *src, size_t len, size_t at) { if (at >= dest->s_len) { at = dest->s_len; } size_t new_size = dest->s_len + len; if (dest->s_max < new_size) { string_change_capacity(dest, new_size); } char *dest_buf = string_ptr(dest); char *from = dest_buf + at; char *to = dest_buf + at + len; memmove(to, from, dest->s_len - at); memcpy(from, src, len); dest_buf[new_size] = '\0'; dest->s_len = new_size; } static void string_insertf( struct b_string_p *dest, size_t at, const char *format, va_list arg) { char buf[1024]; size_t len = vsnprintf(buf, sizeof buf, format, arg); string_insert(dest, buf, len, at); } void b_string_insert_s(b_string *dest, const b_string *src, size_t at) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); struct b_string_p *src_str = b_object_get_private(src, struct b_string_p, B_TYPE_STRING); string_insert(dest_str, string_ptr(src_str), src_str->s_len, at); } void b_string_insert_cstr(b_string *dest, const char *src, size_t at) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); string_insert(dest_str, src, strlen(src), at); } void b_string_insert_cstrf(b_string *dest, size_t at, const char *format, ...) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); va_list arg; va_start(arg, format); string_insertf(dest_str, at, format, arg); va_end(arg); } void b_string_insert_cstrn(b_string *dest, const char *src, size_t len, size_t at) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); string_insert(dest_str, src, len, at); } void b_string_append_s(b_string *dest, const b_string *src) { b_string_insert_s(dest, src, SIZE_MAX); } void b_string_append_cstr(b_string *dest, const char *src) { b_string_insert_cstr(dest, src, SIZE_MAX); } void b_string_append_cstrf(b_string *dest, const char *format, ...) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); va_list arg; va_start(arg, format); string_insertf(dest_str, SIZE_MAX, format, arg); va_end(arg); } void b_string_prepend_s(b_string *dest, const b_string *src) { b_string_insert_s(dest, src, 0); } void b_string_prepend_cstr(b_string *dest, const char *src) { b_string_insert_cstr(dest, src, 0); } void b_string_prepend_cstrf(b_string *dest, const char *format, ...) { struct b_string_p *dest_str = b_object_get_private(dest, struct b_string_p, B_TYPE_STRING); va_list arg; va_start(arg, format); string_insertf(dest_str, 0, format, arg); va_end(arg); } void b_string_clear(b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (str->s_len == 0) { return; } char *s = string_ptr(str); *s = '\0'; str->s_len = 0; } size_t b_string_get_size(const b_string *p, b_strlen_flags flags) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (flags != B_STRLEN_NORMAL) { return b_strlen(string_ptr(str), flags); } return str->s_len; } size_t b_string_get_capacity(const b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); return str->s_max; } char b_string_front(const b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (str->s_len == 0) { return 0; } const char *s = string_ptr(str); return s[0]; } char b_string_back(const b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (str->s_len == 0) { return 0; } const char *s = string_ptr(str); return s[str->s_len - 1]; } void b_string_pop_back(b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (str->s_len == 0) { return; } char *s = string_ptr(str); s[str->s_len - 1] = '\0'; str->s_len--; } const char *b_string_ptr(const b_string *p) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (string_is_inline(str)) { return str->s_data.d_inline; } return str->s_data.d_external; } b_string *b_string_substr(const b_string *p, size_t start, size_t len) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); if (start > str->s_len) { return NULL; } if (start + len > str->s_len) { len = str->s_len - start; } b_string *new_str = b_string_create(); struct b_string_p *new_str_p = b_object_get_private(new_str, struct b_string_p, B_TYPE_STRING); string_reserve(new_str_p, len); const char *src = string_ptr(str) + start; char *dest = string_ptr(new_str_p); memcpy(dest, src, len); new_str_p->s_len = len; return new_str; } static void string_fini(b_string *p, void *priv) { struct b_string_p *str = priv; if (!string_is_inline(str)) { free(string_ptr(str)); } } static void string_to_string(b_string *p, struct b_stream *out) { struct b_string_p *str = b_object_get_private(p, struct b_string_p, B_TYPE_STRING); b_stream_write_string(out, string_ptr(str), NULL); } char *b_strdup(const char *s) { size_t len = strlen(s); char *p = malloc(len + 1); if (!p) { return NULL; } memcpy(p, s, len); p[len] = '\0'; return p; } size_t b_strlen(const char *s, b_strlen_flags flags) { if (!(flags & (B_STRLEN_IGNORE_ESC | B_STRLEN_IGNORE_MOD))) { return strlen(s); } size_t out = 0; for (size_t i = 0; s[i]; i++) { if (s[i] == '\033' && (flags & B_STRLEN_IGNORE_ESC)) { while (!isalpha(s[i]) && s[i]) { i++; } continue; } if (s[i] == '[' && (flags & B_STRLEN_IGNORE_MOD)) { i++; if (s[i] == '[') { out++; continue; } while (s[i] != ']' && s[i]) { i++; } continue; } out++; } return out; } B_TYPE_CLASS_DEFINITION_BEGIN(b_string) B_TYPE_CLASS_INTERFACE_BEGIN(b_object, B_TYPE_OBJECT) B_INTERFACE_ENTRY(to_string) = string_to_string; B_TYPE_CLASS_INTERFACE_END(b_object, B_TYPE_OBJECT) B_TYPE_CLASS_DEFINITION_END(b_string) B_TYPE_DEFINITION_BEGIN(b_string) B_TYPE_ID(0xc986dd64, 0x7bfe, 0x4151, 0x8ffb, 0x251f4651fd35); B_TYPE_CLASS(b_string_class); B_TYPE_EXTENDS(B_TYPE_OBJECT); B_TYPE_INSTANCE_PRIVATE(struct b_string_p); B_TYPE_INSTANCE_INIT(string_init); B_TYPE_DEFINITION_END(b_string)