gb/gb_string.h

567 lines
15 KiB
C

/* gb_string.h - v0.96 - public domain string library - no warranty implied; use at your own risk
A Simple Dynamic Strings Library for C and C++
Version History:
0.96 - Add printf
0.95b - Fix issue #21
0.95a - Change brace style because why not?
0.95 - C90 Support
0.94 - Remove "declare anywhere"
0.93 - Fix typos and errors
0.92 - Add extern "C" if compiling as C++
0.91 - Remove `char * cstr` from String_Header
0.90 - Initial Version
LICENSE
This software is in the public domain. Where that dedication is not
recognized, you are granted a perpetual, irrevocable license to copy,
distribute, and modify this file as you see fit.
How to use:
Do this:
#define GB_STRING_IMPLEMENTATION
before you include this file in *one* C++ file to create the implementation
i.e. it should look like this:
#include ...
#include ...
#include ...
#define GB_STRING_IMPLEMENTATION
#include "gb_string.h"
You can #define GB_ALLOC, and GB_FREE to avoid using malloc,free.
If you prefer to use C++, you can use all the same functions in a
namespace instead, do this:
#define GB_STRING_CPP
before you include the header file
i.e it should look like this:
#define GB_STRING_CPP
#include "gb_string.h"
The C++ version has the advantage that you do not need to reassign variables
i.e.
C version
str = gb_append_cstring(str, "another string");
C++ version
gb::append_cstring(str, "another string");
This could be achieved in C by passing a pointer to the string but for
simplicity and consistency, reassigning the variable is better.
Reasoning:
By default, strings in C are null terminated which means you have to count
the number of character up to the null character to calculate the length.
Many "better" C string libraries will create a struct for a string.
i.e.
struct String {
size_t length;
size_t capacity;
char * cstring;
};
This library tries to augment normal C strings in a better way that is still
compatible with C-style strings.
+--------+-----------------------+-----------------+
| Header | Binary C-style String | Null Terminator |
+--------+-----------------------+-----------------+
|
+-> Pointer returned by functions
Due to the meta-data being stored before the string pointer and every gb string
having an implicit null terminator, gb strings are full compatible with c-style
strings and read-only functions.
Advantages:
* gb strings can be passed to C-style string functions without accessing a struct
member of calling a function, i.e.
printf("%s\n", gb_str);
Many other libraries do either of these:
printf("%s\n", string->cstr);
printf("%s\n", get_cstring(string));
* You can access each character just like a C-style string:
printf("%c %c\n", str[0], str[13]);
* gb strings are singularly allocated. The meta-data is next to the character
array which is better for the cache.
Disadvantages:
* In the C version of these functions, many return the new string. i.e.
str = gb_append_cstring(str, "another string");
In the C++ version, this is made easier with the use of references. i.e.
gb::append_cstring(str, "another string");
* Custom allocators must redefine GB_ALLOC and GB_FREE which can be annoying.
realloc is not used for compatibility with many custom allocators that do not
have a reallocation function.
* This is not compatible with the "gb.h" gbString. That version is a better version
as it allows for custom allocators.
*/
/* Examples: */
/* C example */
#if 0
#include <stdio.h>
#include <stdlib.h>
#define GB_STRING_IMPLEMENTATION
#include "gb_string.h"
int main(int argc, char **argv) {
gbString str = gb_make_string("Hello");
gbString other_str = gb_make_string_length(", ", 2);
str = gb_append_string(str, other_str);
str = gb_append_cstring(str, "world!");
printf("%s\n", str); // Hello, world!
printf("str length = %d\n", gb_string_length(str));
str = gb_set_string(str, "Potato soup");
printf("%s\n", str); // Potato soup
str = gb_set_string(str, "Hello");
other_str = gb_set_string(other_str, "Pizza");
if (gb_strings_are_equal(str, other_str))
printf("Not called\n");
else
printf("Called\n");
str = gb_set_string(str, "Ab.;!...AHello World ??");
str = gb_trim_string(str, "Ab.;!. ?");
printf("%s\n", str); // "Hello World"
gb_free_string(str);
gb_free_string(other_str);
}
#endif
/* C++ example */
#if 0
#include <stdio.h>
#include <stdlib.h>
#define GB_STRING_CPP
#define GB_STRING_IMPLEMENTATION
#include "gb_string.h"
int main(int argc, char **argv) {
using namespace gb;
String str = make_string("Hello");
String other_str = make_string(", ", 2);
append_string(str, other_str);
append_cstring(str, "world!");
printf("%s\n", str); /* Hello, world! */
printf("str length = %d\n", string_length(str));
set_string(str, "Potato soup");
printf("%s\n", str); /* Potato soup */
set_string(str, "Hello");
set_string(other_str, "Pizza");
if (strings_are_equal(str, other_str))
printf("Not called\n");
else
printf("Called\n");
set_string(str, "Ab.;!...AHello World ??");
trim_string(str, "Ab.;!. ?");
printf("%s\n", str); /* "Hello World" */
free_string(str);
free_string(other_str);
}
#endif
#ifndef GB_STRING_INCLUDE_GB_STRING_H
#define GB_STRING_INCLUDE_GB_STRING_H
#if !defined(GB_HAS_SNPRINTF) && \
(__STDC_VERSION__ >= 199101L || __cplusplus >= 201103L)
#define GB_HAS_SNPRINTF
#endif
#ifndef GB_ALLOC
#define GB_ALLOC(sz) malloc(sz)
#define GB_FREE(ptr) free(ptr)
#endif
#ifndef _MSC_VER
#ifdef __cplusplus
#define gb_inline inline
#else
#define gb_inline
#endif
#else
#define gb_inline __forceinline
#endif
#include <string.h> /* Needed for memcpy and cstring functions */
#ifdef __cplusplus
extern "C" {
#endif
typedef char *gbString;
typedef int gbBool;
#if !defined(GB_TRUE) || !defined(GB_FALSE)
#define GB_TRUE 1
#define GB_FALSE 0
#endif
#ifndef GB_SIZE_TYPE
#define GB_SIZE_TYPE
typedef size_t gbUsize;
#endif
#ifndef GB_NULLPTR
#if __cplusplus
#if __cplusplus >= 201103L
#define GB_NULLPTR nullptr
#else
#define GB_NULLPTR 0
#endif
#else
#define GB_NULLPTR (void*)0
#endif
#endif
typedef struct gbStringHeader {
gbUsize len;
gbUsize cap;
} gbStringHeader;
#define GB_STRING_HEADER(s) ((gbStringHeader *)s - 1)
gbString gb_make_string(char const *str);
gbString gb_make_string_length(void const *str, gbUsize len);
void gb_free_string(gbString str);
gbString gb_duplicate_string(gbString const str);
gbUsize gb_string_length(gbString const str);
gbUsize gb_string_capacity(gbString const str);
gbUsize gb_string_available_space(gbString const str);
void gb_clear_string(gbString str);
gbString gb_append_string_length(gbString str, void const *other, gbUsize len);
gbString gb_append_string(gbString str, gbString const other);
gbString gb_append_cstring(gbString str, char const *other);
gbString gb_set_string(gbString str, char const *cstr);
gbString gb_string_make_space_for(gbString str, gbUsize add_len);
gbUsize gb_string_allocation_size(gbString const str);
gbBool gb_strings_are_equal(gbString const lhs, gbString const rhs);
gbString gb_trim_string(gbString str, char const *cut_set);
#ifdef GB_HAS_SNPRINTF
gbString gb_printf(gbString str, char const *fmt, ...);
gbString gb_vprintf(gbString str, char const *fmt, va_list va);
#endif
#ifdef __cplusplus
}
#endif
#if defined(GB_STRING_CPP)
#if !defined(__cplusplus)
#error You need to compile as C++ for the C++ version of gb_string.h to work
#endif
namespace gb
{
typedef gbString String;
typedef gbUsize usize;
gb_inline String make_string(char const *str = "") { return gb_make_string(str); }
gb_inline String make_string(void const *str, usize len) { return gb_make_string_length(str, len); }
gb_inline void free_string(String& str) { gb_free_string(str); str = GB_NULLPTR; }
gb_inline String duplicate_string(const String str) { return gb_duplicate_string(str); }
gb_inline usize string_length(const String str) { return gb_string_length(str); }
gb_inline usize string_capacity(const String str) { return gb_string_capacity(str); }
gb_inline usize string_available_space(const String str) { return gb_string_available_space(str); }
gb_inline void clear_string(String str) { gb_clear_string(str); }
gb_inline void append_string_length(String& str, void const *other, usize len) { str = gb_append_string_length(str, other, len); }
gb_inline void append_string(String& str, const String other) { str = gb_append_string(str, other); }
gb_inline void append_cstring(String& str, char const *other) { str = gb_append_cstring(str, other); }
gb_inline void set_string(String& str, char const *cstr) { str = gb_set_string(str, cstr); }
gb_inline void string_make_space_for(String& str, usize add_len) { str = gb_string_make_space_for(str, add_len); }
gb_inline usize string_allocation_size(const String str) { return gb_string_allocation_size(str); }
gb_inline bool strings_are_equal(const String lhs, const String rhs) { return gb_strings_are_equal(lhs, rhs) == GB_TRUE; }
gb_inline void trim_string(String& str, char const *cut_set) { str = gb_trim_string(str, cut_set); }
#ifdef GB_HAS_SNPRINTF
gb_inline void ref_vprintf(String& str, char const *fmt, va_list va) { str = gb_vprintf(str, fmt, va); }
gb_inline void ref_printf(String& str, char const *fmt, ...) {
va_list va;
va_start(va, fmt);
ref_vprintf(str, fmt, va);
va_end(va);
}
#endif
} /* namespace gb */
#endif /* GB_STRING_CPP */
#endif /* GB_STRING_H */
#ifdef GB_STRING_IMPLEMENTATION
static void gb_set_string_length(gbString str, gbUsize len) {
GB_STRING_HEADER(str)->len = len;
}
static void gb_set_string_capacity(gbString str, gbUsize cap) {
GB_STRING_HEADER(str)->cap = cap;
}
#ifdef GB_HAS_SNPRINTF
#include <limits.h>
#include <stdio.h>
gbString gb_vprintf(gbString s, char const *fmt, va_list va) {
va_list va2;
int nl;
gbUsize clen = gb_string_length(s);
va_copy(va2, va);
nl = vsnprintf(NULL, 0, fmt, va2);
va_end(va2);
if (nl < 0 || nl == INT_MAX) {
gb_free_string(s);
return GB_NULLPTR;
}
s = gb_string_make_space_for(s, nl + 1);
if (s == GB_NULLPTR)
return GB_NULLPTR;
vsnprintf(s + clen, nl + 1, fmt, va);
return s;
}
gbString gb_printf(gbString s, char const *fmt, ...) {
va_list va;
va_start(va, fmt);
s = gb_vprintf(s, fmt, va);
va_end(va);
return s;
}
#endif
gbString gb_make_string_length(void const *init_str, gbUsize len) {
gbString str;
gbStringHeader *header;
gbUsize header_size = sizeof(gbStringHeader);
void *ptr = GB_ALLOC(header_size + len + 1);
if (ptr == GB_NULLPTR)
return GB_NULLPTR;
if (!init_str)
memset(ptr, 0, header_size + len + 1);
str = (char *)ptr + header_size;
header = GB_STRING_HEADER(str);
header->len = len;
header->cap = len;
if (len && init_str)
memcpy(str, init_str, len);
str[len] = '\0';
return str;
}
gbString gb_make_string(char const *str) {
gbUsize len = str ? strlen(str) : 0;
return gb_make_string_length(str, len);
}
void gb_free_string(gbString str) {
if (str == GB_NULLPTR)
return;
GB_FREE((gbStringHeader *)str - 1);
}
gbString gb_duplicate_string(gbString const str) {
return gb_make_string_length(str, gb_string_length(str));
}
gbUsize gb_string_length(gbString const str) {
return GB_STRING_HEADER(str)->len;
}
gbUsize gb_string_capacity(gbString const str) {
return GB_STRING_HEADER(str)->cap;
}
gbUsize gb_string_available_space(gbString const str) {
gbStringHeader *h = GB_STRING_HEADER(str);
if (h->cap > h->len)
return h->cap - h->len;
return 0;
}
void gb_clear_string(gbString str) {
gb_set_string_length(str, 0);
str[0] = '\0';
}
gbString gb_append_string_length(gbString str, void const *other, gbUsize other_len) {
gbUsize curr_len = gb_string_length(str);
str = gb_string_make_space_for(str, other_len);
if (str == GB_NULLPTR)
return GB_NULLPTR;
memcpy(str + curr_len, other, other_len);
str[curr_len + other_len] = '\0';
gb_set_string_length(str, curr_len + other_len);
return str;
}
gbString gb_append_string(gbString str, gbString const other) {
return gb_append_string_length(str, other, gb_string_length(other));
}
gbString gb_append_cstring(gbString str, char const *other) {
return gb_append_string_length(str, other, strlen(other));
}
gbString gb_set_string(gbString str, char const *cstr) {
gbUsize len = strlen(cstr);
if (gb_string_capacity(str) < len) {
str = gb_string_make_space_for(str, len - gb_string_length(str));
if (str == GB_NULLPTR)
return GB_NULLPTR;
}
memcpy(str, cstr, len);
str[len] = '\0';
gb_set_string_length(str, len);
return str;
}
static gb_inline void *gb__string_realloc(void *ptr, gbUsize old_size, gbUsize new_size) {
void *new_ptr;
if (!ptr)
return GB_ALLOC(new_size);
if (new_size < old_size)
new_size = old_size;
if (old_size == new_size)
return ptr;
new_ptr = GB_ALLOC(new_size);
if (!new_ptr)
return GB_NULLPTR;
memcpy(new_ptr, ptr, old_size);
GB_FREE(ptr);
return new_ptr;
}
gbString gb_string_make_space_for(gbString str, gbUsize add_len) {
gbUsize len = gb_string_length(str);
gbUsize new_len = len + add_len;
void *ptr, *new_ptr;
gbUsize available, old_size, new_size;
available = gb_string_available_space(str);
if (available >= add_len) /* Return if there is enough space left */
return str;
ptr = (char *)str - sizeof(gbStringHeader);
old_size = sizeof(gbStringHeader) + gb_string_length(str) + 1;
new_size = sizeof(gbStringHeader) + new_len + 1;
new_ptr = gb__string_realloc(ptr, old_size, new_size);
if (new_ptr == GB_NULLPTR)
return GB_NULLPTR;
str = (char *)new_ptr + sizeof(gbStringHeader);
gb_set_string_capacity(str, new_len);
return str;
}
gbUsize gb_string_allocation_size(gbString const s) {
gbUsize cap = gb_string_capacity(s);
return sizeof(gbStringHeader) + cap;
}
gbBool gb_strings_are_equal(gbString const lhs, gbString const rhs) {
gbUsize lhs_len, rhs_len, i;
lhs_len = gb_string_length(lhs);
rhs_len = gb_string_length(rhs);
if (lhs_len != rhs_len)
return GB_FALSE;
for (i = 0; i < lhs_len; i++) {
if (lhs[i] != rhs[i])
return GB_FALSE;
}
return GB_TRUE;
}
gbString gb_trim_string(gbString str, char const *cut_set) {
char *start, *end, *start_pos, *end_pos;
gbUsize len;
start_pos = start = str;
end_pos = end = str + gb_string_length(str) - 1;
while (start_pos <= end && strchr(cut_set, *start_pos))
start_pos++;
while (end_pos > start_pos && strchr(cut_set, *end_pos))
end_pos--;
len = (start_pos > end_pos) ? 0 : ((end_pos - start_pos)+1);
if (str != start_pos)
memmove(str, start_pos, len);
str[len] = '\0';
gb_set_string_length(str, len);
return str;
}
#endif /* GB_STRING_IMPLEMENTATION */