/* Copyright: Boaz Segev, 2017-2019 License: MIT */ #if defined(__unix__) || defined(__APPLE__) || defined(__linux__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #endif #ifdef _SC_PAGESIZE #define PAGE_SIZE sysconf(_SC_PAGESIZE) #else #define PAGE_SIZE 4096 #endif #include #include #include #include #include #include #include #include #include #include #define FIO_INCLUDE_STR #define FIO_STR_NO_REF #include #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