mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
420 lines
13 KiB
C
420 lines
13 KiB
C
/*
|
|
Copyright: Boaz Segev, 2017-2019
|
|
License: MIT
|
|
*/
|
|
|
|
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _SC_PAGESIZE
|
|
#define PAGE_SIZE sysconf(_SC_PAGESIZE)
|
|
#else
|
|
#define PAGE_SIZE 4096
|
|
#endif
|
|
|
|
#include <fiobject.h>
|
|
|
|
#include <fio_siphash.h>
|
|
#include <fiobj_numbers.h>
|
|
#include <fiobj_str.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define FIO_INCLUDE_STR
|
|
#define FIO_STR_NO_REF
|
|
#include <fio.h>
|
|
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX PAGE_SIZE
|
|
#endif
|
|
|
|
/* *****************************************************************************
|
|
String Type
|
|
***************************************************************************** */
|
|
|
|
typedef struct {
|
|
fiobj_object_header_s head;
|
|
uint64_t hash;
|
|
fio_str_s str;
|
|
} fiobj_str_s;
|
|
|
|
#define obj2str(o) ((fiobj_str_s *)(FIOBJ2PTR(o)))
|
|
|
|
static inline fio_str_info_s fiobj_str_get_cstr(const FIOBJ o) {
|
|
return fio_str_info(&obj2str(o)->str);
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
String VTables
|
|
***************************************************************************** */
|
|
|
|
static fio_str_info_s fio_str2str(const FIOBJ o) {
|
|
return fiobj_str_get_cstr(o);
|
|
}
|
|
|
|
static void fiobj_str_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg) {
|
|
fio_str_free(&obj2str(o)->str);
|
|
fio_free(FIOBJ2PTR(o));
|
|
(void)task;
|
|
(void)arg;
|
|
}
|
|
|
|
static size_t fiobj_str_is_eq(const FIOBJ self, const FIOBJ other) {
|
|
return fio_str_iseq(&obj2str(self)->str, &obj2str(other)->str);
|
|
}
|
|
|
|
static intptr_t fio_str2i(const FIOBJ o) {
|
|
char *pos = fio_str_data(&obj2str(o)->str);
|
|
return fio_atol(&pos);
|
|
}
|
|
static double fio_str2f(const FIOBJ o) {
|
|
char *pos = fio_str_data(&obj2str(o)->str);
|
|
return fio_atof(&pos);
|
|
}
|
|
|
|
static size_t fio_str2bool(const FIOBJ o) {
|
|
return fio_str_len(&obj2str(o)->str) != 0;
|
|
}
|
|
|
|
uintptr_t fiobject___noop_count(const FIOBJ o);
|
|
|
|
const fiobj_object_vtable_s FIOBJECT_VTABLE_STRING = {
|
|
.class_name = "String",
|
|
.dealloc = fiobj_str_dealloc,
|
|
.to_i = fio_str2i,
|
|
.to_f = fio_str2f,
|
|
.to_str = fio_str2str,
|
|
.is_eq = fiobj_str_is_eq,
|
|
.is_true = fio_str2bool,
|
|
.count = fiobject___noop_count,
|
|
};
|
|
|
|
/* *****************************************************************************
|
|
String API
|
|
***************************************************************************** */
|
|
|
|
/** Creates a buffer String object. Remember to use `fiobj_free`. */
|
|
FIOBJ fiobj_str_buf(size_t capa) {
|
|
if (capa)
|
|
capa = capa + 1;
|
|
else
|
|
capa = PAGE_SIZE;
|
|
|
|
fiobj_str_s *s = fio_malloc(sizeof(*s));
|
|
if (!s) {
|
|
perror("ERROR: fiobj string couldn't allocate memory");
|
|
exit(errno);
|
|
}
|
|
*s = (fiobj_str_s){
|
|
.head =
|
|
{
|
|
.ref = 1,
|
|
.type = FIOBJ_T_STRING,
|
|
},
|
|
.str = FIO_STR_INIT,
|
|
};
|
|
if (capa) {
|
|
fio_str_capa_assert(&s->str, capa);
|
|
}
|
|
return ((uintptr_t)s | FIOBJECT_STRING_FLAG);
|
|
}
|
|
|
|
/** Creates a String object. Remember to use `fiobj_free`. */
|
|
FIOBJ fiobj_str_new(const char *str, size_t len) {
|
|
fiobj_str_s *s = fio_malloc(sizeof(*s));
|
|
if (!s) {
|
|
perror("ERROR: fiobj string couldn't allocate memory");
|
|
exit(errno);
|
|
}
|
|
*s = (fiobj_str_s){
|
|
.head =
|
|
{
|
|
.ref = 1,
|
|
.type = FIOBJ_T_STRING,
|
|
},
|
|
.str = FIO_STR_INIT,
|
|
};
|
|
if (str && len) {
|
|
fio_str_write(&s->str, str, len);
|
|
}
|
|
return ((uintptr_t)s | FIOBJECT_STRING_FLAG);
|
|
}
|
|
|
|
/**
|
|
* Creates a String object. Remember to use `fiobj_free`.
|
|
*
|
|
* It's possible to wrap a previosly allocated memory block in a FIOBJ String
|
|
* object, as long as it was allocated using `fio_malloc`.
|
|
*
|
|
* The ownership of the memory indicated by `str` will "move" to the object and
|
|
* will be freed (using `fio_free`) once the object's reference count drops to
|
|
* zero.
|
|
*/
|
|
FIOBJ fiobj_str_move(char *str, size_t len, size_t capacity) {
|
|
fiobj_str_s *s = fio_malloc(sizeof(*s));
|
|
if (!s) {
|
|
perror("ERROR: fiobj string couldn't allocate memory");
|
|
exit(errno);
|
|
}
|
|
*s = (fiobj_str_s){
|
|
.head =
|
|
{
|
|
.ref = 1,
|
|
.type = FIOBJ_T_STRING,
|
|
},
|
|
.str = FIO_STR_INIT_EXISTING(str, len, capacity),
|
|
};
|
|
return ((uintptr_t)s | FIOBJECT_STRING_FLAG);
|
|
}
|
|
|
|
/**
|
|
* Returns a thread-static temporary string. Avoid calling `fiobj_dup` or
|
|
* `fiobj_free`.
|
|
*/
|
|
FIOBJ fiobj_str_tmp(void) {
|
|
static __thread fiobj_str_s tmp = {
|
|
.head =
|
|
{
|
|
.ref = ((~(uint32_t)0) >> 4),
|
|
.type = FIOBJ_T_STRING,
|
|
},
|
|
.str = {.small = 1},
|
|
};
|
|
tmp.str.frozen = 0;
|
|
fio_str_resize(&tmp.str, 0);
|
|
return ((uintptr_t)&tmp | FIOBJECT_STRING_FLAG);
|
|
}
|
|
|
|
/** Prevents the String object from being changed. */
|
|
void fiobj_str_freeze(FIOBJ str) {
|
|
if (FIOBJ_TYPE_IS(str, FIOBJ_T_STRING))
|
|
fio_str_freeze(&obj2str(str)->str);
|
|
}
|
|
|
|
/** Confirms the requested capacity is available and allocates as required. */
|
|
size_t fiobj_str_capa_assert(FIOBJ str, size_t size) {
|
|
|
|
assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING));
|
|
if (obj2str(str)->str.frozen)
|
|
return 0;
|
|
fio_str_info_s state = fio_str_capa_assert(&obj2str(str)->str, size);
|
|
return state.capa;
|
|
}
|
|
|
|
/** Return's a String's capacity, if any. */
|
|
size_t fiobj_str_capa(FIOBJ str) {
|
|
assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING));
|
|
return fio_str_capa(&obj2str(str)->str);
|
|
}
|
|
|
|
/** Resizes a String object, allocating more memory if required. */
|
|
void fiobj_str_resize(FIOBJ str, size_t size) {
|
|
assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING));
|
|
fio_str_resize(&obj2str(str)->str, size);
|
|
obj2str(str)->hash = 0;
|
|
return;
|
|
}
|
|
|
|
/** Deallocates any unnecessary memory (if supported by OS). */
|
|
void fiobj_str_compact(FIOBJ str) {
|
|
assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING));
|
|
fio_str_compact(&obj2str(str)->str);
|
|
return;
|
|
}
|
|
|
|
/** Empties a String's data. */
|
|
void fiobj_str_clear(FIOBJ str) {
|
|
assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING));
|
|
fio_str_resize(&obj2str(str)->str, 0);
|
|
obj2str(str)->hash = 0;
|
|
}
|
|
|
|
/**
|
|
* Writes data at the end of the string, resizing the string as required.
|
|
* Returns the new length of the String
|
|
*/
|
|
size_t fiobj_str_write(FIOBJ dest, const char *data, size_t len) {
|
|
assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (obj2str(dest)->str.frozen)
|
|
return 0;
|
|
obj2str(dest)->hash = 0;
|
|
return fio_str_write(&obj2str(dest)->str, data, len).len;
|
|
}
|
|
|
|
/**
|
|
* Writes a number at the end of the String using normal base 10 notation.
|
|
*
|
|
* Returns the new length of the String
|
|
*/
|
|
size_t fiobj_str_write_i(FIOBJ dest, int64_t num) {
|
|
assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (obj2str(dest)->str.frozen)
|
|
return 0;
|
|
obj2str(dest)->hash = 0;
|
|
return fio_str_write_i(&obj2str(dest)->str, num).len;
|
|
}
|
|
|
|
/**
|
|
* Writes data at the end of the string, resizing the string as required.
|
|
* Returns the new length of the String
|
|
*/
|
|
size_t fiobj_str_printf(FIOBJ dest, const char *format, ...) {
|
|
assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (obj2str(dest)->str.frozen)
|
|
return 0;
|
|
obj2str(dest)->hash = 0;
|
|
va_list argv;
|
|
va_start(argv, format);
|
|
fio_str_info_s state = fio_str_vprintf(&obj2str(dest)->str, format, argv);
|
|
va_end(argv);
|
|
return state.len;
|
|
}
|
|
|
|
size_t fiobj_str_vprintf(FIOBJ dest, const char *format, va_list argv) {
|
|
assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (obj2str(dest)->str.frozen)
|
|
return 0;
|
|
obj2str(dest)->hash = 0;
|
|
fio_str_info_s state = fio_str_vprintf(&obj2str(dest)->str, format, argv);
|
|
return state.len;
|
|
}
|
|
|
|
/** Dumps the `filename` file's contents at the end of a String. If `limit ==
|
|
* 0`, than the data will be read until EOF.
|
|
*
|
|
* If the file can't be located, opened or read, or if `start_at` is beyond
|
|
* the EOF position, NULL is returned.
|
|
*
|
|
* Remember to use `fiobj_free`.
|
|
*/
|
|
size_t fiobj_str_readfile(FIOBJ dest, const char *filename, intptr_t start_at,
|
|
intptr_t limit) {
|
|
fio_str_info_s state =
|
|
fio_str_readfile(&obj2str(dest)->str, filename, start_at, limit);
|
|
return state.len;
|
|
}
|
|
|
|
/**
|
|
* Writes data at the end of the string, resizing the string as required.
|
|
* Returns the new length of the String
|
|
*/
|
|
size_t fiobj_str_concat(FIOBJ dest, FIOBJ obj) {
|
|
assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
|
|
if (obj2str(dest)->str.frozen)
|
|
return 0;
|
|
obj2str(dest)->hash = 0;
|
|
fio_str_info_s o = fiobj_obj2cstr(obj);
|
|
if (o.len == 0)
|
|
return fio_str_len(&obj2str(dest)->str);
|
|
return fio_str_write(&obj2str(dest)->str, o.data, o.len).len;
|
|
}
|
|
|
|
/**
|
|
* Calculates a String's SipHash value for use as a HashMap key.
|
|
*/
|
|
uint64_t fiobj_str_hash(FIOBJ o) {
|
|
assert(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING));
|
|
// if (obj2str(o)->is_small) {
|
|
// return fiobj_hash_string(STR_INTENAL_STR(o), STR_INTENAL_LEN(o));
|
|
// } else
|
|
if (obj2str(o)->hash) {
|
|
return obj2str(o)->hash;
|
|
}
|
|
fio_str_info_s state = fio_str_info(&obj2str(o)->str);
|
|
obj2str(o)->hash = fiobj_hash_string(state.data, state.len);
|
|
return obj2str(o)->hash;
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
Tests
|
|
***************************************************************************** */
|
|
|
|
#if DEBUG
|
|
void fiobj_test_string(void) {
|
|
fprintf(stderr, "=== Testing Strings\n");
|
|
fprintf(stderr, "* Internal String Capacity %u \n",
|
|
(unsigned int)FIO_STR_SMALL_CAPA);
|
|
#define TEST_ASSERT(cond, ...) \
|
|
if (!(cond)) { \
|
|
fprintf(stderr, "* " __VA_ARGS__); \
|
|
fprintf(stderr, "Testing failed.\n"); \
|
|
exit(-1); \
|
|
}
|
|
#define STR_EQ(o, str) \
|
|
TEST_ASSERT((fiobj_str_getlen(o) == strlen(str) && \
|
|
!memcmp(fiobj_str_mem_addr(o), str, strlen(str))), \
|
|
"String not equal to " str)
|
|
FIOBJ o = fiobj_str_new("Hello", 5);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), "Small String isn't string!\n");
|
|
TEST_ASSERT(obj2str(o)->str.small, "Hello isn't small\n");
|
|
fiobj_str_write(o, " World", 6);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
|
|
"Hello World String isn't string!\n");
|
|
TEST_ASSERT(obj2str(o)->str.small, "Hello World isn't small\n");
|
|
TEST_ASSERT(fiobj_obj2cstr(o).len == 11,
|
|
"Invalid small string length (%u != 11)!\n",
|
|
(unsigned int)fiobj_obj2cstr(o).len)
|
|
fiobj_str_write(o, " World, you crazy longer sleep loving person :-)", 48);
|
|
TEST_ASSERT(!obj2str(o)->str.small, "Crazier shouldn't be small\n");
|
|
fiobj_free(o);
|
|
|
|
o = fiobj_str_new(
|
|
"hello my dear friend, I hope that your are well and happy.", 58);
|
|
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), "Long String isn't string!\n");
|
|
TEST_ASSERT(!obj2str(o)->str.small,
|
|
"Long String is small! (capa: %lu, len: %lu)\n",
|
|
fio_str_capa(&obj2str(o)->str), fio_str_len(&obj2str(o)->str));
|
|
TEST_ASSERT(fiobj_obj2cstr(o).len == 58,
|
|
"Invalid long string length (%lu != 58)!\n",
|
|
fiobj_obj2cstr(o).len)
|
|
uint64_t hash = fiobj_str_hash(o);
|
|
TEST_ASSERT(!obj2str(o)->str.frozen, "String forzen when only hashing!\n");
|
|
fiobj_str_freeze(o);
|
|
TEST_ASSERT(obj2str(o)->str.frozen, "String not forzen!\n");
|
|
fiobj_str_write(o, " World", 6);
|
|
TEST_ASSERT(hash == fiobj_str_hash(o),
|
|
"String hash changed after hashing - not frozen?\n");
|
|
TEST_ASSERT(fiobj_obj2cstr(o).len == 58,
|
|
"String was edited after hashing - not frozen!\n (%lu): %s",
|
|
(unsigned long)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data);
|
|
fiobj_free(o);
|
|
|
|
o = fiobj_str_buf(1);
|
|
fiobj_str_printf(o, "%u", 42);
|
|
TEST_ASSERT(fio_str_len(&obj2str(o)->str) == 2,
|
|
"fiobj_strprintf length error.\n");
|
|
TEST_ASSERT(fiobj_obj2num(o), "fiobj_strprintf integer error.\n");
|
|
TEST_ASSERT(!memcmp(fiobj_obj2cstr(o).data, "42", 2),
|
|
"fiobj_strprintf string error.\n");
|
|
fiobj_free(o);
|
|
|
|
o = fiobj_str_buf(4);
|
|
for (int i = 0; i < 16000; ++i) {
|
|
fiobj_str_write(o, "a", 1);
|
|
}
|
|
TEST_ASSERT(fio_str_len(&obj2str(o)->str) == 16000,
|
|
"16K fiobj_str_write not 16K.\n");
|
|
TEST_ASSERT(fio_str_capa(&obj2str(o)->str) >= 16000,
|
|
"16K fiobj_str_write capa not enough.\n");
|
|
fiobj_free(o);
|
|
|
|
o = fiobj_str_buf(0);
|
|
TEST_ASSERT(fiobj_str_readfile(o, __FILE__, 0, 0),
|
|
"`fiobj_str_readfile` - file wasn't read!");
|
|
TEST_ASSERT(!memcmp(fiobj_obj2cstr(o).data, "/*", 2),
|
|
"`fiobj_str_readfile` error, start of file doesn't match:\n%s",
|
|
fiobj_obj2cstr(o).data);
|
|
fiobj_free(o);
|
|
|
|
fprintf(stderr, "* passed.\n");
|
|
}
|
|
#endif
|