#include #include "apfl.h" #include "alloc.h" #include "internal.h" #include "resizable.h" #include "hashmap.h" #include "value.h" struct apfl_list_data { unsigned refcount; struct apfl_value *items; size_t len; size_t cap; }; struct apfl_dict_data { unsigned refcount; struct apfl_hashmap map; }; static apfl_hash value_hash(const struct apfl_value); apfl_list apfl_list_incref(apfl_list list) { if (list == NULL) { return NULL; } list->refcount++; return list; } size_t apfl_list_len(apfl_list list) { return list->len; } void apfl_list_unref(struct apfl_allocator allocator, apfl_list list) { if (list == NULL || !apfl_refcount_dec(&list->refcount)) { return; } for (size_t i = 0; i < list->len; i++) { apfl_value_deinit(allocator, &list->items[i]); } FREE_LIST(allocator, list->items, list->cap); FREE_OBJ(allocator, list); } static bool dict_keys_eq(void *opaque, const void *a, const void *b) { (void)opaque; return apfl_value_eq(*((struct apfl_value *) a), *((struct apfl_value *) b)); } static apfl_hash dict_calc_hash(void *opaque, const void *key) { (void)opaque; return value_hash(*(const struct apfl_value *)key); } static void dict_destroy_key_or_value(void *opaque, void *kv) { struct apfl_allocator *allocator = opaque; apfl_value_deinit(*allocator, kv); } static void dict_copy_key_or_value(void *opaque, void *_dest, void *_src) { (void)opaque; struct apfl_value *dest = _dest; struct apfl_value *src = _src; *dest = apfl_value_incref(*src); } static void dict_on_deinit(void *opaque) { struct apfl_allocator *allocator = opaque; FREE_OBJ(*allocator, allocator); } static bool init_hashmap_for_dict(struct apfl_allocator allocator, struct apfl_hashmap *map) { struct apfl_allocator *allocator_ptr = ALLOC_OBJ(allocator, struct apfl_allocator); if (allocator_ptr == NULL) { return false; } *allocator_ptr = allocator; bool ok = apfl_hashmap_init( map, allocator, (struct apfl_hashmap_callbacks) { .opaque = allocator_ptr, .keys_eq = dict_keys_eq, .calc_hash = dict_calc_hash, .destroy_key = dict_destroy_key_or_value, .destroy_value = dict_destroy_key_or_value, .copy_key = dict_copy_key_or_value, .copy_value = dict_copy_key_or_value, .on_deinit = dict_on_deinit, }, sizeof(struct apfl_value), sizeof(struct apfl_value) ); if (!ok) { FREE_OBJ(allocator, allocator_ptr); } return ok; } apfl_dict apfl_dict_incref(apfl_dict dict) { if (dict == NULL) { return NULL; } dict->refcount++; return dict; } void apfl_dict_unref(struct apfl_allocator allocator, apfl_dict dict) { if (dict == NULL || !apfl_refcount_dec(&dict->refcount)) { return; } apfl_hashmap_deinit(&dict->map); FREE_OBJ(allocator, dict); } static void print(unsigned indent, FILE *out, struct apfl_value value, bool skip_first_indent) { struct apfl_string_view sv; unsigned first_indent = skip_first_indent ? 0 : indent; switch (value.type) { case APFL_VALUE_NIL: apfl_print_indented(first_indent, out, "nil"); return; case APFL_VALUE_BOOLEAN: apfl_print_indented(first_indent, out, value.boolean ? "true" : "false"); return; case APFL_VALUE_NUMBER: apfl_print_indented(first_indent, out, "%f", value.number); return; case APFL_VALUE_STRING: sv = apfl_string_view_from(value.string); apfl_print_indented(first_indent, out, "\"" APFL_STR_FMT "\"", APFL_STR_FMT_ARGS(sv)); return; case APFL_VALUE_LIST: if (value.list->len == 0) { apfl_print_indented(first_indent, out, "[]"); return; } apfl_print_indented(first_indent, out, "[\n"); for (size_t i = 0; i < value.list->len; i++) { print(indent+1, out, value.list->items[i], false); apfl_print_indented(indent, out, "\n"); } apfl_print_indented(indent, out, "]"); return; case APFL_VALUE_DICT: if (apfl_hashmap_count(value.dict->map) == 0) { apfl_print_indented(first_indent, out, "[->]"); return; } struct apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(&value.dict->map); apfl_print_indented(first_indent, out, "[\n"); for (; !apfl_hashmap_cursor_is_end(cur); apfl_hashmap_cursor_next(&cur)) { struct apfl_value *k; struct apfl_value *v; apfl_hashmap_cursor_peek_key(cur, (void **)&k); apfl_hashmap_cursor_peek_value(cur, (void **)&v); print(indent+1, out, *k, false); fprintf(out, " -> "); print(indent+1, out, *v, true); fprintf(out, "\n"); } apfl_print_indented(indent, out, "]"); return; } fprintf(out, "Unknown value? (%d)\n", (int)value.type); } struct apfl_value apfl_value_move(struct apfl_value *src) { struct apfl_value out = *src; src->type = APFL_VALUE_NIL; return out; } struct apfl_value apfl_value_incref(struct apfl_value value) { switch (value.type) { case APFL_VALUE_NIL: case APFL_VALUE_BOOLEAN: case APFL_VALUE_NUMBER: // Nothing to do return value; case APFL_VALUE_STRING: value.string = apfl_refcounted_string_incref(value.string); return value; case APFL_VALUE_LIST: value.list = apfl_list_incref(value.list); return value; case APFL_VALUE_DICT: value.dict = apfl_dict_incref(value.dict); return value; } assert(false); return value; } void apfl_value_print(struct apfl_value value, FILE *out) { print(0, out, value, false); fprintf(out, "\n"); } static bool list_eq(apfl_list a, apfl_list b) { if (a == b) { return true; } if (a->len != b->len) { return true; } for (size_t i = 0; i < a->len; i++) { if (!apfl_value_eq(a->items[i], b->items[i])) { return false; } } return true; } static bool dict_eq(const apfl_dict a, const apfl_dict b) { if (a == b) { return true; } struct apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(&a->map); size_t total = 0; for (; !apfl_hashmap_cursor_is_end(cur); apfl_hashmap_cursor_next(&cur)) { struct apfl_value *key; struct apfl_value *val; apfl_hashmap_cursor_peek_key(cur, (void **)&key); apfl_hashmap_cursor_peek_value(cur, (void **)&val); struct apfl_value *other_val; if (!apfl_hashmap_peek(&b->map, &key, (void **)&other_val)) { return false; } bool eq = apfl_value_eq(*val, *other_val); if (!eq) { return false; } total++; } return total == apfl_hashmap_count(b->map); } bool apfl_value_eq(const struct apfl_value a, const struct apfl_value b) { if (a.type != b.type) { return false; } switch (a.type) { case APFL_VALUE_NIL: return true; case APFL_VALUE_BOOLEAN: return a.boolean == b.boolean; case APFL_VALUE_NUMBER: return a.number == b.number; case APFL_VALUE_STRING: return apfl_string_eq(a.string, b.string); case APFL_VALUE_LIST: return list_eq(a.list, b.list); case APFL_VALUE_DICT: return dict_eq(a.dict, b.dict); } assert(false); return false; } struct apfl_editable_list_data { struct apfl_allocator allocator; struct apfl_value *items; size_t len; size_t cap; }; apfl_editable_list apfl_editable_list_new(struct apfl_allocator allocator) { apfl_editable_list elist = ALLOC_OBJ(allocator, struct apfl_editable_list_data); if (elist == NULL) { return NULL; } elist->allocator = allocator; elist->items = NULL; elist->len = 0; elist->cap = 0; return elist; } static bool editable_list_append_values(apfl_editable_list elist, struct apfl_value *values, size_t len) { if (!apfl_resizable_ensure_cap_for_more_elements( elist->allocator, sizeof(struct apfl_value), (void **)&elist->items, elist->len, &elist->cap, len )) { return false; } for (size_t i = 0; i < len; i++) { elist->items[elist->len] = apfl_value_incref(values[i]); elist->len++; } return true; } bool apfl_editable_list_append(apfl_editable_list elist, struct apfl_value value) { bool ok = editable_list_append_values(elist, &value, 1); apfl_value_deinit(elist->allocator, &value); return ok; } bool apfl_editable_list_append_list(apfl_editable_list elist, apfl_list list) { bool ok = editable_list_append_values(elist, list->items, list->len); apfl_list_unref(elist->allocator, list); return ok; } void apfl_editable_list_destroy(apfl_editable_list elist) { if (elist == NULL) { return; } if (elist->items != NULL) { for (size_t i = 0; i < elist->len; i++) { apfl_value_deinit(elist->allocator, &elist->items[i]); } FREE_LIST(elist->allocator, elist->items, elist->cap); } FREE_OBJ(elist->allocator, elist); } apfl_list apfl_editable_list_finalize(apfl_editable_list elist) { apfl_list list = ALLOC_OBJ(elist->allocator, struct apfl_list_data); if (list == NULL) { apfl_editable_list_destroy(elist); return NULL; } list->refcount = 1; list->items = elist->items; // TODO: Maybe shrink memory with realloc? list->len = elist->len; list->cap = elist->cap; FREE_OBJ(elist->allocator, elist); return list; } struct apfl_editable_dict_data { struct apfl_allocator allocator; struct apfl_hashmap map; }; apfl_editable_dict apfl_editable_dict_new(struct apfl_allocator allocator) { apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data); if (ed == NULL) { return NULL; } ed->allocator = allocator; if (!init_hashmap_for_dict(allocator, &ed->map)) { FREE_OBJ(allocator, ed); return NULL; } return ed; } apfl_editable_dict apfl_editable_dict_new_from_dict(struct apfl_allocator allocator, apfl_dict dict) { if (dict->refcount == 0) { return NULL; } apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data); if (ed == NULL) { apfl_dict_unref(allocator, dict); return NULL; } ed->allocator = allocator; if (dict->refcount == 1) { // We're the only one having a reference to the dict. We can directly use it's hashmap! ed->map = apfl_hashmap_move(&dict->map); } else { // There are other places referencing the dict, we need to create a shallow copy first. if (!apfl_hashmap_copy(&ed->map, dict->map)) { FREE_OBJ(allocator, ed); apfl_dict_unref(allocator, dict); return NULL; } } apfl_dict_unref(allocator, dict); return ed; } bool apfl_editable_dict_get( apfl_editable_dict ed, struct apfl_value key, struct apfl_value *out ) { bool ok = apfl_hashmap_get(&ed->map, &key, out); apfl_value_deinit(ed->allocator, &key); return ok; } bool apfl_editable_dict_set(apfl_editable_dict ed, struct apfl_value key, struct apfl_value value) { if (ed == NULL) { return false; } bool ok = apfl_hashmap_set(&ed->map, &key, &value); apfl_value_deinit(ed->allocator, &key); apfl_value_deinit(ed->allocator, &value); return ok; } void apfl_editable_dict_delete(apfl_editable_dict ed, struct apfl_value key) { if (ed == NULL) { return; } apfl_hashmap_delete(&ed->map, &key); apfl_value_deinit(ed->allocator, &key); } apfl_dict apfl_editable_dict_finalize(apfl_editable_dict ed) { if (ed == NULL) { return NULL; } apfl_dict dict = ALLOC_OBJ(ed->allocator, struct apfl_dict_data); if (dict == NULL) { apfl_editable_dict_destroy(ed); return NULL; } dict->refcount = 1; dict->map = apfl_hashmap_move(&ed->map); FREE_OBJ(ed->allocator, ed); return dict; } void apfl_editable_dict_destroy(apfl_editable_dict ed) { if (ed == NULL) { return; } apfl_hashmap_deinit(&ed->map); FREE_OBJ(ed->allocator, ed); } void apfl_value_deinit(struct apfl_allocator allocator, struct apfl_value *value) { switch (value->type) { case APFL_VALUE_NIL: case APFL_VALUE_BOOLEAN: case APFL_VALUE_NUMBER: goto ok; case APFL_VALUE_STRING: apfl_refcounted_string_unref(allocator, value->string); value->string = NULL; goto ok; case APFL_VALUE_LIST: apfl_list_unref(allocator, value->list); value->list = NULL; goto ok; case APFL_VALUE_DICT: apfl_dict_unref(allocator, value->dict); value->dict = NULL; goto ok; } assert(false); ok: value->type = APFL_VALUE_NIL; } bool apfl_list_get_item(struct apfl_allocator allocator, apfl_list list, size_t index, struct apfl_value *out) { if (index >= list->len) { apfl_list_unref(allocator, list); return false; } *out = apfl_value_incref(list->items[index]); apfl_list_unref(allocator, list); return true; } bool apfl_dict_get_item(struct apfl_allocator allocator, apfl_dict dict, struct apfl_value key, struct apfl_value *out) { bool ok = apfl_hashmap_get(&dict->map, &key, out); apfl_dict_unref(allocator, dict); apfl_value_deinit(allocator, &key); return ok; } static enum get_item_result get_item(struct apfl_allocator allocator, /*borrowed*/ struct apfl_value container, /*borrowed*/ struct apfl_value key, struct apfl_value *out) { if (container.type == APFL_VALUE_LIST) { if (key.type != APFL_VALUE_NUMBER) { return GET_ITEM_WRONG_KEY_TYPE; } return apfl_list_get_item( allocator, apfl_list_incref(container.list), (size_t)key.number, out ) ? GET_ITEM_OK : GET_ITEM_KEY_DOESNT_EXIST; } else if (container.type == APFL_VALUE_DICT) { return apfl_dict_get_item( allocator, apfl_dict_incref(container.dict), apfl_value_incref(key), out ) ? GET_ITEM_OK : GET_ITEM_KEY_DOESNT_EXIST; } else { return GET_ITEM_NOT_A_CONTAINER; } } enum get_item_result apfl_value_get_item(struct apfl_allocator allocator, struct apfl_value container, struct apfl_value key, struct apfl_value *out) { enum get_item_result result = get_item(allocator, container, key, out); apfl_value_deinit(allocator, &container); apfl_value_deinit(allocator, &key); return result; } static apfl_hash value_hash(const struct apfl_value value) { apfl_hash hash = apfl_hash_fnv1a(&value.type, sizeof(enum apfl_value_type)); struct apfl_string_view sv; switch (value.type) { case APFL_VALUE_NIL: goto ok; case APFL_VALUE_BOOLEAN: hash = apfl_hash_fnv1a_add(&value.boolean, sizeof(bool), hash); goto ok; case APFL_VALUE_NUMBER: hash = apfl_hash_fnv1a_add(&value.number, sizeof(apfl_number), hash); goto ok; case APFL_VALUE_STRING: sv = apfl_string_view_from(value.string); hash = apfl_hash_fnv1a_add(sv.bytes, sv.len, hash); goto ok; case APFL_VALUE_LIST: for (size_t i = 0; i < value.list->len; i++) { apfl_hash item_hash = value_hash(value.list->items[i]); hash = apfl_hash_fnv1a_add(&item_hash, sizeof(apfl_hash), hash); } goto ok; case APFL_VALUE_DICT: // TODO: This results in all dictionaries having the same hash. Since // it's rather unusual to have dictionaries as keys, this is fine // for now, but should be improved nonetheless! goto ok; } assert(false); ok: return hash; }