#include #include "apfl.h" #include "internal.h" #include "resizable.h" #include "hashmap.h" struct apfl_list_data { unsigned refcount; struct apfl_value *items; size_t len; }; struct apfl_dict_data { unsigned refcount; 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; } void apfl_list_unref(apfl_list list) { if (list == NULL || !apfl_refcount_dec(&list->refcount)) { return; } for (size_t i = 0; i < list->len; i++) { apfl_value_deinit(&list->items[i]); } free(list->items); free(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) { (void)opaque; apfl_value_deinit(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 const struct apfl_hashmap_callbacks dict_hashmap_callbacks = { .opaque = NULL, .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, }; static apfl_hashmap new_hashmap_for_dict(void) { return apfl_hashmap_new( dict_hashmap_callbacks, sizeof(struct apfl_value), sizeof(struct apfl_value) ); } apfl_dict apfl_dict_incref(apfl_dict dict) { if (dict == NULL) { return NULL; } dict->refcount++; return dict; } void apfl_dict_unref(apfl_dict dict) { if (dict == NULL || !apfl_refcount_dec(&dict->refcount)) { return; } apfl_hashmap_destroy(dict->map); free(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; } apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(value.dict->map); if (cur == NULL) { return; // TODO: Handle failure in a better way } apfl_print_indented(first_indent, out, "[\n"); for (; !apfl_hashmap_cursor_is_end(cur); apfl_hashmap_cursor_next(cur)) { struct apfl_value k, v; apfl_hashmap_cursor_get_key(cur, &k); apfl_hashmap_cursor_get_value(cur, &v); print(indent+1, out, k, false); apfl_value_deinit(&k); fprintf(out, " -> "); print(indent+1, out, v, true); apfl_value_deinit(&v); fprintf(out, "\n"); } apfl_hashmap_cursor_destroy(cur); 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_inner(apfl_hashmap_cursor cur, const apfl_dict b) { 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_get_key(cur, &key); apfl_hashmap_cursor_get_value(cur, &val); struct apfl_value other_val; if (!apfl_hashmap_get(b->map, &key, &other_val)) { apfl_value_deinit(&key); apfl_value_deinit(&val); return false; } bool eq = apfl_value_eq(val, other_val); apfl_value_deinit(&key); apfl_value_deinit(&val); apfl_value_deinit(&other_val); if (!eq) { return false; } total++; } return total == apfl_hashmap_count(b->map); } static bool dict_eq(const apfl_dict a, const apfl_dict b) { if (a == b) { return true; } apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(a->map); if (cur == NULL) { assert(false); // TODO: It's rather ugly that this can fail and that we return false then! return false; } bool eq = dict_eq_inner(cur, b); apfl_hashmap_cursor_destroy(cur); return eq; } 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_value *items; size_t len; size_t cap; }; apfl_editable_list apfl_editable_list_new(void) { apfl_editable_list elist = ALLOC(struct apfl_editable_list_data); if (elist == NULL) { return NULL; } elist->items = NULL; elist->len = 0; elist->cap = 0; return elist; } apfl_editable_list apfl_editable_list_new_from_list(apfl_list list) { if (list == NULL) { return NULL; } apfl_editable_list elist = ALLOC(struct apfl_editable_list_data); if (elist == NULL) { apfl_list_unref(list); return NULL; } if (list->refcount == 1) { // We're the only one with a reference to the list, so we can directly use the items of the list! elist->items = list->items; elist->len = list->len; elist->cap = list->len; } else { // We fisrt need to create a shallow copy of the elements if ((elist->items = ALLOC_LIST(struct apfl_value, list->len)) == NULL) { apfl_list_unref(list); free(elist); return NULL; } for (size_t i = 0; i < list->len; i++) { elist->items[i] = apfl_value_incref(list->items[i]); } elist->len = list->len; elist->cap = list->len; } apfl_list_unref(list); 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( 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(&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(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->items[i]); } free(elist->items); } free(elist); } apfl_list apfl_editable_list_finalize(apfl_editable_list elist) { apfl_list list = ALLOC(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; free(elist); return list; } struct apfl_editable_dict_data { apfl_hashmap map; }; apfl_editable_dict apfl_editable_dict_new(void) { apfl_editable_dict ed = ALLOC(struct apfl_editable_dict_data); if (ed == NULL) { return NULL; } if ((ed->map = new_hashmap_for_dict()) == NULL) { free(ed); return NULL; } return ed; } apfl_editable_dict apfl_editable_dict_new_from_dict(apfl_dict dict) { if (dict->refcount == 0) { return NULL; } apfl_editable_dict ed = ALLOC(struct apfl_editable_dict_data); if (ed == NULL) { apfl_dict_unref(dict); return NULL; } if (dict->refcount == 1) { // We're the only one having a reference to the dict. We can directly use it's hashmap! MOVEPTR(ed->map, dict->map); } else { // There are other places referencing the dict, we need to create a shallow copy first. if ((ed->map = apfl_hashmap_copy(dict->map)) == NULL) { free(ed); apfl_dict_unref(dict); return NULL; } } apfl_dict_unref(dict); return ed; } bool apfl_editable_dict_set(apfl_editable_dict ed, struct apfl_value key, struct apfl_value value) { if (ed == NULL || ed->map == NULL) { return false; } bool ok = apfl_hashmap_set(ed->map, &key, &value); apfl_value_deinit(&key); apfl_value_deinit(&value); return ok; } void apfl_editable_dict_delete(apfl_editable_dict ed, struct apfl_value key) { if (ed == NULL || ed->map == NULL) { return; } apfl_hashmap_delete(ed->map, &key); apfl_value_deinit(&key); } apfl_dict apfl_editable_dict_finalize(apfl_editable_dict ed) { if (ed == NULL || ed->map == NULL) { return NULL; } apfl_dict dict = ALLOC(struct apfl_dict_data); if (dict == NULL) { apfl_editable_dict_destroy(ed); return NULL; } dict->refcount = 1; MOVEPTR(dict->map, ed->map); free(ed); return dict; } void apfl_editable_dict_destroy(apfl_editable_dict ed) { if (ed == NULL) { return; } apfl_hashmap_destroy(ed->map); free(ed); } void apfl_value_deinit(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(value->string); value->string = NULL; goto ok; case APFL_VALUE_LIST: apfl_list_unref(value->list); value->list = NULL; goto ok; case APFL_VALUE_DICT: apfl_dict_unref(value->dict); value->dict = NULL; goto ok; } assert(false); ok: value->type = APFL_VALUE_NIL; } bool apfl_list_get_item(apfl_list list, size_t index, struct apfl_value *out) { if (index >= list->len) { apfl_list_unref(list); return false; } *out = apfl_value_incref(list->items[index]); apfl_list_unref(list); return true; } bool apfl_dict_get_item(apfl_dict dict, struct apfl_value key, struct apfl_value *out) { bool ok = apfl_hashmap_get(dict->map, &key, out); apfl_dict_unref(dict); apfl_value_deinit(&key); return ok; } static enum apfl_value_get_item_result get_item(/*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 APFL_VALUE_GET_ITEM_WRONG_KEY_TYPE; } return apfl_list_get_item( apfl_list_incref(container.list), (size_t)key.number, out ) ? APFL_VALUE_GET_ITEM_OK : APFL_VALUE_GET_ITEM_KEY_DOESNT_EXIST; } else if (container.type == APFL_VALUE_DICT) { return apfl_dict_get_item( apfl_dict_incref(container.dict), apfl_value_incref(key), out ) ? APFL_VALUE_GET_ITEM_OK : APFL_VALUE_GET_ITEM_KEY_DOESNT_EXIST; } else { return APFL_VALUE_GET_ITEM_NOT_A_CONTAINER; } } enum apfl_value_get_item_result apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out) { enum apfl_value_get_item_result result = get_item(container, key, out); apfl_value_deinit(&container); apfl_value_deinit(&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; }