| /* |
| Copyright: Boaz Segev, 2017-2018 |
| License: MIT |
| */ |
| |
| /** |
| This facil.io core library provides wrappers around complex and (or) dynamic |
| types, abstracting some complexity and making dynamic type related tasks easier. |
| */ |
| #include <fiobject.h> |
| #undef FIO_FORCE_MALLOC /* just in case */ |
| #include <fio.h> |
| |
| #include <fio_ary.h> |
| |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| /* ***************************************************************************** |
| Use the facil.io allocator when available, but override it if it's missing. |
| ***************************************************************************** */ |
| |
| #include <fio.h> |
| |
| #pragma weak fio_malloc |
| void *fio_malloc(size_t size) { |
| void *m = malloc(size); |
| if (m) |
| memset(m, 0, size); |
| return m; |
| } |
| |
| #pragma weak fio_calloc |
| void *__attribute__((weak)) fio_calloc(size_t size, size_t count) { |
| return calloc(size, count); |
| } |
| |
| #pragma weak fio_free |
| void __attribute__((weak)) fio_free(void *ptr) { free(ptr); } |
| |
| #pragma weak fio_realloc |
| void *__attribute__((weak)) fio_realloc(void *ptr, size_t new_size) { |
| return realloc(ptr, new_size); |
| } |
| |
| #pragma weak fio_realloc2 |
| void *__attribute__((weak)) |
| fio_realloc2(void *ptr, size_t new_size, size_t valid_len) { |
| return realloc(ptr, new_size); |
| (void)valid_len; |
| } |
| |
| #pragma weak fio_mmap |
| void *__attribute__((weak)) fio_mmap(size_t size) { return fio_malloc(size); } |
| |
| /** The logging level */ |
| #if DEBUG |
| #pragma weak FIO_LOG_LEVEL |
| size_t __attribute__((weak)) FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEBUG; |
| #else |
| #pragma weak FIO_LOG_LEVEL |
| size_t __attribute__((weak)) FIO_LOG_LEVEL = FIO_LOG_LEVEL_INFO; |
| #endif |
| |
| /* ***************************************************************************** |
| the `fiobj_each2` function |
| ***************************************************************************** */ |
| struct task_packet_s { |
| int (*task)(FIOBJ obj, void *arg); |
| void *arg; |
| fio_ary_s *stack; |
| FIOBJ next; |
| uintptr_t counter; |
| uint8_t stop; |
| uint8_t incomplete; |
| }; |
| |
| static int fiobj_task_wrapper(FIOBJ o, void *p_) { |
| struct task_packet_s *p = p_; |
| ++p->counter; |
| int ret = p->task(o, p->arg); |
| if (ret == -1) { |
| p->stop = 1; |
| return -1; |
| } |
| if (FIOBJ_IS_ALLOCATED(o) && FIOBJECT2VTBL(o)->each) { |
| p->incomplete = 1; |
| p->next = o; |
| return -1; |
| } |
| return 0; |
| } |
| /** |
| * Single layer iteration using a callback for each nested fio object. |
| * |
| * Accepts any `FIOBJ ` type but only collections (Arrays and Hashes) are |
| * processed. The container itself (the Array or the Hash) is **not** processed |
| * (unlike `fiobj_each2`). |
| * |
| * The callback task function must accept an object and an opaque user pointer. |
| * |
| * Hash objects pass along a `FIOBJ_T_COUPLET` object, containing |
| * references for both the key and the object. Keys shouldn't be altered once |
| * placed as a key (or the Hash will break). Collections (Arrays / Hashes) can't |
| * be used as keeys. |
| * |
| * If the callback returns -1, the loop is broken. Any other value is ignored. |
| * |
| * Returns the "stop" position, i.e., the number of items processed + the |
| * starting point. |
| */ |
| size_t fiobj_each2(FIOBJ o, int (*task)(FIOBJ obj, void *arg), void *arg) { |
| if (!o || !FIOBJ_IS_ALLOCATED(o) || (FIOBJECT2VTBL(o)->each == NULL)) { |
| task(o, arg); |
| return 1; |
| } |
| /* run task for root object */ |
| if (task(o, arg) == -1) |
| return 1; |
| uintptr_t pos = 0; |
| fio_ary_s stack = FIO_ARY_INIT; |
| struct task_packet_s packet = { |
| .task = task, |
| .arg = arg, |
| .stack = &stack, |
| .counter = 1, |
| }; |
| do { |
| if (!pos) |
| packet.next = 0; |
| packet.incomplete = 0; |
| pos = FIOBJECT2VTBL(o)->each(o, pos, fiobj_task_wrapper, &packet); |
| if (packet.stop) |
| goto finish; |
| if (packet.incomplete) { |
| fio_ary_push(&stack, (void *)pos); |
| fio_ary_push(&stack, (void *)o); |
| } |
| |
| if (packet.next) { |
| fio_ary_push(&stack, (void *)0); |
| fio_ary_push(&stack, (void *)packet.next); |
| } |
| o = (FIOBJ)fio_ary_pop(&stack); |
| pos = (uintptr_t)fio_ary_pop(&stack); |
| } while (o); |
| finish: |
| fio_ary_free(&stack); |
| return packet.counter; |
| } |
| |
| /* ***************************************************************************** |
| Free complex objects (objects with nesting) |
| ***************************************************************************** */ |
| |
| static void fiobj_dealloc_task(FIOBJ o, void *stack_) { |
| // if (!o) |
| // fprintf(stderr, "* WARN: freeing a NULL no-object\n"); |
| // else |
| // fprintf(stderr, "* freeing object %s\n", fiobj_obj2cstr(o).data); |
| if (!o || !FIOBJ_IS_ALLOCATED(o)) |
| return; |
| if (OBJREF_REM(o)) |
| return; |
| if (!FIOBJECT2VTBL(o)->each || !FIOBJECT2VTBL(o)->count(o)) { |
| FIOBJECT2VTBL(o)->dealloc(o, NULL, NULL); |
| return; |
| } |
| fio_ary_s *s = stack_; |
| fio_ary_push(s, (void *)o); |
| } |
| /** |
| * Decreases an object's reference count, releasing memory and |
| * resources. |
| * |
| * This function affects nested objects, meaning that when an Array or |
| * a Hash object is passed along, it's children (nested objects) are |
| * also freed. |
| */ |
| void fiobj_free_complex_object(FIOBJ o) { |
| fio_ary_s stack = FIO_ARY_INIT; |
| do { |
| FIOBJECT2VTBL(o)->dealloc(o, fiobj_dealloc_task, &stack); |
| } while ((o = (FIOBJ)fio_ary_pop(&stack))); |
| fio_ary_free(&stack); |
| } |
| |
| /* ***************************************************************************** |
| Is Equal? |
| ***************************************************************************** */ |
| #include <fiobj_hash.h> |
| |
| static inline int fiobj_iseq_simple(const FIOBJ o, const FIOBJ o2) { |
| if (o == o2) |
| return 1; |
| if (!o || !o2) |
| return 0; /* they should have compared equal before. */ |
| if (!FIOBJ_IS_ALLOCATED(o) || !FIOBJ_IS_ALLOCATED(o2)) |
| return 0; /* they should have compared equal before. */ |
| if (FIOBJECT2HEAD(o)->type != FIOBJECT2HEAD(o2)->type) |
| return 0; /* non-type equality is a barriar to equality. */ |
| if (!FIOBJECT2VTBL(o)->is_eq(o, o2)) |
| return 0; |
| return 1; |
| } |
| |
| static int fiobj_iseq____internal_complex__task(FIOBJ o, void *ary_) { |
| fio_ary_s *ary = ary_; |
| fio_ary_push(ary, (void *)o); |
| if (fiobj_hash_key_in_loop()) |
| fio_ary_push(ary, (void *)fiobj_hash_key_in_loop()); |
| return 0; |
| } |
| |
| /** used internally for complext nested tests (Array / Hash types) */ |
| int fiobj_iseq____internal_complex__(FIOBJ o, FIOBJ o2) { |
| // if (FIOBJECT2VTBL(o)->each && FIOBJECT2VTBL(o)->count(o)) |
| // return int fiobj_iseq____internal_complex__(const FIOBJ o, const FIOBJ |
| // o2); |
| fio_ary_s left = FIO_ARY_INIT, right = FIO_ARY_INIT, queue = FIO_ARY_INIT; |
| do { |
| fiobj_each1(o, 0, fiobj_iseq____internal_complex__task, &left); |
| fiobj_each1(o2, 0, fiobj_iseq____internal_complex__task, &right); |
| while (fio_ary_count(&left)) { |
| o = (FIOBJ)fio_ary_pop(&left); |
| o2 = (FIOBJ)fio_ary_pop(&right); |
| if (!fiobj_iseq_simple(o, o2)) |
| goto unequal; |
| if (FIOBJ_IS_ALLOCATED(o) && FIOBJECT2VTBL(o)->each && |
| FIOBJECT2VTBL(o)->count(o)) { |
| fio_ary_push(&queue, (void *)o); |
| fio_ary_push(&queue, (void *)o2); |
| } |
| } |
| o2 = (FIOBJ)fio_ary_pop(&queue); |
| o = (FIOBJ)fio_ary_pop(&queue); |
| if (!fiobj_iseq_simple(o, o2)) |
| goto unequal; |
| } while (o); |
| fio_ary_free(&left); |
| fio_ary_free(&right); |
| fio_ary_free(&queue); |
| return 1; |
| unequal: |
| fio_ary_free(&left); |
| fio_ary_free(&right); |
| fio_ary_free(&queue); |
| return 0; |
| } |
| |
| /* ***************************************************************************** |
| Defaults / NOOPs |
| ***************************************************************************** */ |
| |
| void fiobject___noop_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg) { |
| (void)o; |
| (void)task; |
| (void)arg; |
| } |
| void fiobject___simple_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), |
| void *arg) { |
| fio_free(FIOBJ2PTR(o)); |
| (void)task; |
| (void)arg; |
| } |
| |
| uintptr_t fiobject___noop_count(const FIOBJ o) { |
| (void)o; |
| return 0; |
| } |
| size_t fiobject___noop_is_eq(const FIOBJ o1, const FIOBJ o2) { |
| (void)o1; |
| (void)o2; |
| return 0; |
| } |
| |
| fio_str_info_s fiobject___noop_to_str(const FIOBJ o) { |
| (void)o; |
| return (fio_str_info_s){.len = 0, .data = NULL}; |
| } |
| intptr_t fiobject___noop_to_i(const FIOBJ o) { |
| (void)o; |
| return 0; |
| } |
| double fiobject___noop_to_f(const FIOBJ o) { |
| (void)o; |
| return 0; |
| } |
| |
| #if DEBUG |
| |
| #include <fiobj_ary.h> |
| #include <fiobj_numbers.h> |
| |
| static int fiobject_test_task(FIOBJ o, void *arg) { |
| ++((uintptr_t *)arg)[0]; |
| if (!o) |
| fprintf(stderr, "* WARN: counting a NULL no-object\n"); |
| // else |
| // fprintf(stderr, "* counting object %s\n", fiobj_obj2cstr(o).data); |
| return 0; |
| (void)o; |
| } |
| |
| void fiobj_test_core(void) { |
| #define TEST_ASSERT(cond, ...) \ |
| if (!(cond)) { \ |
| fprintf(stderr, __VA_ARGS__); \ |
| fprintf(stderr, "Testing failed.\n"); \ |
| exit(-1); \ |
| } |
| fprintf(stderr, "=== Testing Primitives\n"); |
| FIOBJ o = fiobj_null(); |
| TEST_ASSERT(o == (FIOBJ)FIOBJ_T_NULL, "fiobj_null isn't NULL!\n"); |
| TEST_ASSERT(FIOBJ_TYPE(0) == FIOBJ_T_NULL, "NULL isn't NULL!\n"); |
| TEST_ASSERT(FIOBJ_TYPE_IS(0, FIOBJ_T_NULL), "NULL isn't NULL! (2)\n"); |
| TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_null()), |
| "fiobj_null claims to be allocated!\n"); |
| TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_true()), |
| "fiobj_true claims to be allocated!\n"); |
| TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_false()), |
| "fiobj_false claims to be allocated!\n"); |
| TEST_ASSERT(FIOBJ_TYPE(fiobj_true()) == FIOBJ_T_TRUE, |
| "fiobj_true isn't FIOBJ_T_TRUE!\n"); |
| TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_true(), FIOBJ_T_TRUE), |
| "fiobj_true isn't FIOBJ_T_TRUE! (2)\n"); |
| TEST_ASSERT(FIOBJ_TYPE(fiobj_false()) == FIOBJ_T_FALSE, |
| "fiobj_false isn't FIOBJ_T_TRUE!\n"); |
| TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_false(), FIOBJ_T_FALSE), |
| "fiobj_false isn't FIOBJ_T_TRUE! (2)\n"); |
| fiobj_free(o); /* testing for crash*/ |
| fprintf(stderr, "* passed.\n"); |
| fprintf(stderr, "=== Testing fioj_each2\n"); |
| o = fiobj_ary_new2(4); |
| FIOBJ tmp = fiobj_ary_new(); |
| fiobj_ary_push(o, tmp); |
| fiobj_ary_push(o, fiobj_true()); |
| fiobj_ary_push(o, fiobj_null()); |
| fiobj_ary_push(o, fiobj_num_new(10)); |
| fiobj_ary_push(tmp, fiobj_num_new(13)); |
| fiobj_ary_push(tmp, fiobj_hash_new()); |
| FIOBJ key = fiobj_str_new("my key", 6); |
| fiobj_hash_set(fiobj_ary_entry(tmp, -1), key, fiobj_true()); |
| fiobj_free(key); |
| /* we have root array + 4 children (w/ array) + 2 children (w/ hash) + 1 */ |
| uintptr_t count = 0; |
| size_t each_ret = 0; |
| TEST_ASSERT(fiobj_each2(o, fiobject_test_task, (void *)&count) == 8, |
| "fiobj_each1 didn't count everything... (%d != %d)", (int)count, |
| (int)each_ret); |
| TEST_ASSERT(count == 8, "Something went wrong with the counter task... (%d)", |
| (int)count) |
| fprintf(stderr, "* passed.\n"); |
| fprintf(stderr, "=== Testing fioj_iseq with nested items\n"); |
| FIOBJ o2 = fiobj_ary_new2(4); |
| tmp = fiobj_ary_new(); |
| fiobj_ary_push(o2, tmp); |
| fiobj_ary_push(o2, fiobj_true()); |
| fiobj_ary_push(o2, fiobj_null()); |
| fiobj_ary_push(o2, fiobj_num_new(10)); |
| fiobj_ary_push(tmp, fiobj_num_new(13)); |
| fiobj_ary_push(tmp, fiobj_hash_new()); |
| key = fiobj_str_new("my key", 6); |
| fiobj_hash_set(fiobj_ary_entry(tmp, -1), key, fiobj_true()); |
| fiobj_free(key); |
| TEST_ASSERT(!fiobj_iseq(o, FIOBJ_INVALID), |
| "Array and FIOBJ_INVALID can't be equal!"); |
| TEST_ASSERT(!fiobj_iseq(o, fiobj_null()), |
| "Array and fiobj_null can't be equal!"); |
| TEST_ASSERT(fiobj_iseq(o, o2), "Arrays aren't euqal!"); |
| fiobj_free(o); |
| fiobj_free(o2); |
| TEST_ASSERT(fiobj_iseq(fiobj_null(), fiobj_null()), |
| "fiobj_null() not equal to self!"); |
| TEST_ASSERT(fiobj_iseq(fiobj_false(), fiobj_false()), |
| "fiobj_false() not equal to self!"); |
| TEST_ASSERT(fiobj_iseq(fiobj_true(), fiobj_true()), |
| "fiobj_true() not equal to self!"); |
| TEST_ASSERT(!fiobj_iseq(fiobj_null(), fiobj_false()), |
| "fiobj_null eqal to fiobj_false!"); |
| TEST_ASSERT(!fiobj_iseq(fiobj_null(), fiobj_true()), |
| "fiobj_null eqal to fiobj_true!"); |
| fprintf(stderr, "* passed.\n"); |
| } |
| |
| #endif |