684 lines
14 KiB
C
684 lines
14 KiB
C
#include "string.h"
|
|
|
|
#include <blue/core/stream.h>
|
|
#include <blue/core/stringstream.h>
|
|
#include <blue/object/string.h>
|
|
#include <blue/object/type.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void string_release(struct b_object *obj);
|
|
static void string_to_string(struct b_object *obj, struct b_stream *out);
|
|
|
|
static struct b_object_type string_type = {
|
|
.t_name = "corelib::string",
|
|
.t_flags = B_OBJECT_FUNDAMENTAL,
|
|
.t_id = B_OBJECT_TYPE_STRING,
|
|
.t_instance_size = sizeof(struct b_string),
|
|
.t_release = string_release,
|
|
.t_to_string = string_to_string,
|
|
};
|
|
|
|
struct b_string *b_string_create(void)
|
|
{
|
|
struct b_string *str
|
|
= (struct b_string *)b_object_type_instantiate(&string_type);
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
|
|
str->s_len = 0;
|
|
str->s_max = STRING_INLINE_CAPACITY;
|
|
|
|
return str;
|
|
}
|
|
|
|
static bool string_is_inline(const struct b_string *str)
|
|
{
|
|
/* strings cannot go below STRING_INLINE_CAPACITY capacity */
|
|
return str->s_max == STRING_INLINE_CAPACITY;
|
|
}
|
|
|
|
static char *string_ptr(struct b_string *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 *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 *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 *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 *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;
|
|
}
|
|
|
|
struct b_string *b_string_create_from_cstr(const char *s)
|
|
{
|
|
struct b_string *str = b_string_create();
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!s) {
|
|
return str;
|
|
}
|
|
|
|
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 str;
|
|
}
|
|
|
|
struct b_string *b_string_create_from_c(char c, size_t count)
|
|
{
|
|
struct b_string *str = b_string_create();
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
|
|
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 str;
|
|
}
|
|
|
|
struct b_string *b_string_duplicate(const struct b_string *str)
|
|
{
|
|
struct b_string *new_str = b_string_create();
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
|
|
string_change_capacity(new_str, str->s_len);
|
|
const char *src = b_string_ptr(str);
|
|
char *dst = string_ptr(new_str);
|
|
|
|
memcpy(dst, src, str->s_len);
|
|
new_str->s_len = str->s_len;
|
|
|
|
return new_str;
|
|
}
|
|
|
|
char *b_string_steal(struct b_string *str)
|
|
{
|
|
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;
|
|
}
|
|
|
|
b_status b_string_reserve(struct b_string *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_replace(
|
|
struct b_string *str, size_t start, size_t length, const char *new_data)
|
|
{
|
|
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 = b_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 *str, const char *new_data)
|
|
{
|
|
size_t new_len = strlen(new_data);
|
|
b_string_reserve(str, new_len);
|
|
char *dest = (char *)b_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 *str, size_t start, size_t length)
|
|
{
|
|
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 *str, int (*transformer)(int))
|
|
{
|
|
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)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
b_string_release(str);
|
|
|
|
return B_SUCCESS;
|
|
}
|
|
|
|
static enum b_status stream_getc(struct b_stream *stream, int *out)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
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)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
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)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
enum b_status status = B_SUCCESS;
|
|
|
|
if (stream->s_cursor + count > str->s_max) {
|
|
status = b_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)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
|
|
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)
|
|
{
|
|
struct b_string *str = stream->s_ptr;
|
|
|
|
size_t new_capacity = str->s_len + len;
|
|
return b_string_reserve(str, new_capacity);
|
|
}
|
|
|
|
enum b_status b_string_open_stream(struct 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_string_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 *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 *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(struct b_string *dest, const struct b_string *src, size_t at)
|
|
{
|
|
string_insert(dest, b_string_ptr(src), src->s_len, at);
|
|
}
|
|
|
|
void b_string_insert_cstr(struct b_string *dest, const char *src, size_t at)
|
|
{
|
|
string_insert(dest, src, strlen(src), at);
|
|
}
|
|
|
|
void b_string_insert_cstrf(struct b_string *dest, size_t at, const char *format, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
string_insertf(dest, at, format, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
void b_string_insert_cstrn(b_string *dest, const char *src, size_t len, size_t at)
|
|
{
|
|
string_insert(dest, src, len, at);
|
|
}
|
|
|
|
void b_string_append_s(struct b_string *dest, const struct b_string *src)
|
|
{
|
|
b_string_insert_s(dest, src, SIZE_MAX);
|
|
}
|
|
|
|
void b_string_append_cstr(struct b_string *dest, const char *src)
|
|
{
|
|
b_string_insert_cstr(dest, src, SIZE_MAX);
|
|
}
|
|
|
|
void b_string_append_cstrf(struct b_string *dest, const char *format, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
string_insertf(dest, SIZE_MAX, format, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
void b_string_prepend_s(struct b_string *dest, const struct b_string *src)
|
|
{
|
|
b_string_insert_s(dest, src, 0);
|
|
}
|
|
|
|
void b_string_prepend_cstr(struct b_string *dest, const char *src)
|
|
{
|
|
b_string_insert_cstr(dest, src, 0);
|
|
}
|
|
|
|
void b_string_prepend_cstrf(struct b_string *dest, const char *format, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
string_insertf(dest, 0, format, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
void b_string_clear(struct b_string *str)
|
|
{
|
|
if (str->s_len == 0) {
|
|
return;
|
|
}
|
|
|
|
char *s = string_ptr(str);
|
|
*s = '\0';
|
|
str->s_len = 0;
|
|
}
|
|
|
|
size_t b_string_get_size(const struct b_string *str, b_strlen_flags flags)
|
|
{
|
|
if (flags != B_STRLEN_NORMAL) {
|
|
return b_strlen(b_string_ptr(str), flags);
|
|
}
|
|
|
|
return str->s_len;
|
|
}
|
|
|
|
size_t b_string_get_capacity(const struct b_string *str)
|
|
{
|
|
return str->s_max;
|
|
}
|
|
|
|
char b_string_front(const struct b_string *str)
|
|
{
|
|
if (str->s_len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
const char *s = b_string_ptr(str);
|
|
return s[0];
|
|
}
|
|
|
|
char b_string_back(const struct b_string *str)
|
|
{
|
|
if (str->s_len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
const char *s = b_string_ptr(str);
|
|
return s[str->s_len - 1];
|
|
}
|
|
|
|
void b_string_pop_back(struct b_string *str)
|
|
{
|
|
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 struct b_string *str)
|
|
{
|
|
if (string_is_inline(str)) {
|
|
return str->s_data.d_inline;
|
|
}
|
|
|
|
return str->s_data.d_external;
|
|
}
|
|
|
|
struct b_string *b_string_substr(const struct b_string *str, size_t start, size_t len)
|
|
{
|
|
if (start > b_string_get_size(str, B_STRLEN_NORMAL)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (start + len > b_string_get_size(str, B_STRLEN_NORMAL)) {
|
|
len = b_string_get_size(str, B_STRLEN_NORMAL) - start;
|
|
}
|
|
|
|
struct b_string *newstr = b_string_create();
|
|
b_string_reserve(newstr, len);
|
|
|
|
const char *src = b_string_ptr(str) + start;
|
|
char *dest = string_ptr(newstr);
|
|
|
|
memcpy(dest, src, len);
|
|
newstr->s_len = len;
|
|
|
|
return newstr;
|
|
}
|
|
|
|
static void string_release(struct b_object *obj)
|
|
{
|
|
struct b_string *str = B_STRING(obj);
|
|
if (!string_is_inline(str)) {
|
|
free(string_ptr(str));
|
|
}
|
|
}
|
|
|
|
static void string_to_string(struct b_object *obj, struct b_stream *out)
|
|
{
|
|
b_string *str = B_STRING(obj);
|
|
b_stream_write_fmt(out, NULL, "%s", b_string_ptr(str));
|
|
}
|
|
|
|
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_object_type_id b_string_type_id(void)
|
|
{
|
|
return (b_object_type_id)&string_type;
|
|
}
|