diff --git a/src/Makefile.am b/src/Makefile.am index f6bba9f..2a10221 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,6 +6,7 @@ libapfl_a_SOURCES = libapfl_a_SOURCES += error.c libapfl_a_SOURCES += eval.c libapfl_a_SOURCES += expr.c +libapfl_a_SOURCES += hashmap.c libapfl_a_SOURCES += internal.c libapfl_a_SOURCES += position.c libapfl_a_SOURCES += resizable.c @@ -18,6 +19,7 @@ libapfl_a_SOURCES += value.c apfl_internal_headers = apfl_internal_headers += common.h +apfl_internal_headers += hashmap.c apfl_internal_headers += internal.h apfl_internal_headers += resizable.h diff --git a/src/apfl.h b/src/apfl.h index e271b74..b4ff64e 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -513,9 +513,15 @@ enum apfl_value_type { APFL_VALUE_NUMBER, APFL_VALUE_STRING, APFL_VALUE_LIST, - // TODO: dict, functions/closures + APFL_VALUE_DICT, + // TODO: functions/closures }; +struct apfl_dict_data; +typedef struct apfl_dict_data *apfl_dict; + +void apfl_dict_unref(apfl_dict); + struct apfl_value { enum apfl_value_type type; union { @@ -523,13 +529,31 @@ struct apfl_value { apfl_number number; apfl_refcounted_string string; struct apfl_list *list; + apfl_dict dict; }; }; +bool apfl_value_eq(const struct apfl_value, const struct apfl_value); +struct apfl_value apfl_value_move(struct apfl_value *src); bool apfl_value_copy(struct apfl_value *dst, struct apfl_value src); void apfl_value_print(struct apfl_value, FILE *); void apfl_value_deinit(struct apfl_value *); +struct apfl_editable_dict_data; +typedef struct apfl_editable_dict_data *apfl_editable_dict; + +apfl_editable_dict apfl_editable_dict_new(void); +apfl_editable_dict apfl_editable_dict_new_from_dict(apfl_dict); +bool apfl_editable_dict_set(apfl_editable_dict, struct apfl_value key, struct apfl_value value); +void apfl_editable_dict_delete(apfl_editable_dict, struct apfl_value key); +void apfl_editable_dict_destroy(apfl_editable_dict); + +/* Finalize the dictionary and return a non-editable dictionary. + * This also destroys the editable dictionary object, so it's no longer safe to use it. + * Returns NULL on failure + */ +apfl_dict apfl_editable_dict_finalize(apfl_editable_dict); + enum apfl_function_response_type { APFL_FUNCTION_RESPONSE_OK, APFL_FUNCTION_RESPONSE_ERROR, diff --git a/src/eval.c b/src/eval.c index b53e21d..9fb7fe1 100644 --- a/src/eval.c +++ b/src/eval.c @@ -175,18 +175,71 @@ evaluate_list(apfl_ctx ctx, struct apfl_expr_list *list) }; } +static struct apfl_result +evaluate_dict(apfl_ctx ctx, struct apfl_expr_dict *dict) +{ + apfl_editable_dict ed = apfl_editable_dict_new(); + + if (ed == NULL) { + return fatal(); + } + + for (size_t i = 0; i < dict->len; i++) { + struct apfl_result result; + struct apfl_value key, value; + + result = evaluate(ctx, dict->items[i].k); + if (result.type != APFL_RESULT_OK) { + apfl_editable_dict_destroy(ed); + return result; + } + key = result.value; + + result = evaluate(ctx, dict->items[i].v); + if (result.type != APFL_RESULT_OK) { + apfl_editable_dict_destroy(ed); + apfl_value_deinit(&key); + return result; + } + value = result.value; + + if (!apfl_editable_dict_set( + ed, + apfl_value_move(&key), + apfl_value_move(&value) + )) { + apfl_editable_dict_destroy(ed); + return fatal(); + } + } + + apfl_dict out = apfl_editable_dict_finalize(ed); + if (out == NULL) { + return fatal(); + } + + return (struct apfl_result) { + .type = APFL_RESULT_OK, + .value = { + .type = APFL_VALUE_DICT, + .dict = out, + }, + }; +} + + static struct apfl_result evaluate(apfl_ctx ctx, struct apfl_expr *expr) { - (void)ctx; switch (expr->type) { case APFL_EXPR_CONSTANT: return evaluate_constant(&expr->constant); case APFL_EXPR_LIST: return evaluate_list(ctx, &expr->list); + case APFL_EXPR_DICT: + return evaluate_dict(ctx, &expr->dict); case APFL_EXPR_VAR: case APFL_EXPR_CALL: - case APFL_EXPR_DICT: case APFL_EXPR_SIMPLE_FUNC: case APFL_EXPR_COMPLEX_FUNC: case APFL_EXPR_ASSIGNMENT: diff --git a/src/hashmap.c b/src/hashmap.c index bc5502a..39748af 100644 --- a/src/hashmap.c +++ b/src/hashmap.c @@ -49,7 +49,7 @@ apfl_hash_fnv1a(const void *data, size_t len) #define INVOKE_CALLBACK(map, cb, ...) (map)->callbacks.cb((map)->callbacks.opaque, __VA_ARGS__) static bool -keys_eq(apfl_hashmap map, const void *a, const void *b) +keys_eq(const apfl_hashmap map, const void *a, const void *b) { if (HAS_CALLBACK(map, keys_eq)) { return INVOKE_CALLBACK(map, keys_eq, a, b); @@ -118,7 +118,7 @@ calc_new_cap(size_t old_cap) #define KVADDR(base, elemsize, off) (((char*)(base)) + ((elemsize)*(off))) static bool -find_key_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, size_t *off) +find_key_in_bucket(const apfl_hashmap map, struct bucket *bucket, const void *key, size_t *off) { size_t keysize = map->keysize; @@ -181,7 +181,7 @@ set_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, const vo } static bool -get_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, void *value) +get_in_bucket(const apfl_hashmap map, struct bucket *bucket, const void *key, void *value) { size_t i; if (!find_key_in_bucket(map, bucket, key, &i)) { @@ -237,8 +237,8 @@ delete_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key) #define INITIAL_NBUCKETS 16 // Must be a power of 2 -apfl_hashmap -apfl_hashmap_new(struct apfl_hashmap_callbacks callbacks, size_t keysize, size_t valsize) +static apfl_hashmap +hashmap_new(struct apfl_hashmap_callbacks callbacks, size_t nbuckets, size_t keysize, size_t valsize) { apfl_hashmap map = malloc(sizeof(struct apfl_hashmap_struct)); if (map == NULL) { @@ -248,13 +248,13 @@ apfl_hashmap_new(struct apfl_hashmap_callbacks callbacks, size_t keysize, size_t map->callbacks = callbacks; map->keysize = keysize; map->valsize = valsize; - map->nbuckets = INITIAL_NBUCKETS; - map->buckets = malloc(sizeof(struct bucket) * INITIAL_NBUCKETS); + map->nbuckets = nbuckets; + map->buckets = malloc(sizeof(struct bucket) * nbuckets); if (map->buckets == NULL) { goto fail; } - for (size_t i = 0; i < INITIAL_NBUCKETS; i++) { + for (size_t i = 0; i < nbuckets; i++) { map->buckets[i] = (struct bucket) { .keys = NULL, .values = NULL, @@ -270,6 +270,12 @@ fail: return NULL; } +apfl_hashmap +apfl_hashmap_new(struct apfl_hashmap_callbacks callbacks, size_t keysize, size_t valsize) +{ + return hashmap_new(callbacks, INITIAL_NBUCKETS, keysize, valsize); +} + void apfl_hashmap_delete(apfl_hashmap map, const void *key) { @@ -277,7 +283,7 @@ apfl_hashmap_delete(apfl_hashmap map, const void *key) } bool -apfl_hashmap_get(apfl_hashmap map, const void *key, void *value) +apfl_hashmap_get(const apfl_hashmap map, const void *key, void *value) { return get_in_bucket(map, bucket_by_key(map, key), key, value); } @@ -301,6 +307,16 @@ destroy_bucket(apfl_hashmap map, struct bucket *bucket) bucket->cap = 0; } +size_t +apfl_hashmap_count(const apfl_hashmap map) +{ + size_t count = 0; + for (size_t i = 0; i < map->nbuckets; i++) { + count += map->buckets[i].len; + } + return count; +} + void apfl_hashmap_destroy(apfl_hashmap map) { @@ -318,6 +334,63 @@ apfl_hashmap_destroy(apfl_hashmap map) free(map); } +apfl_hashmap +apfl_hashmap_copy(apfl_hashmap src) +{ + size_t keysize = src->keysize; + size_t valsize = src->valsize; + + apfl_hashmap dst = hashmap_new(src->callbacks, src->nbuckets, keysize, valsize); + if (dst == NULL) { + return NULL; + } + + for (size_t i = 0; i < dst->nbuckets; i++) { + struct bucket *srcbucket = &src->buckets[i]; + struct bucket *dstbucket = &dst->buckets[i]; + size_t len = srcbucket->len; + + dstbucket->keys = malloc(keysize * len); + dstbucket->values = malloc(valsize * len); + if (dstbucket->keys == NULL || dstbucket->values == NULL) { + free(dstbucket->keys); + dstbucket->keys = NULL; + free(dstbucket->values); + dstbucket->values = NULL; + + goto fail; + } + + dstbucket->cap = len; + + for (; dstbucket->len < len; dstbucket->len++) { + void *keyaddr = KVADDR(dstbucket->keys, keysize, dstbucket->len); + + if (!copy_key( + dst, + keyaddr, + KVADDR(srcbucket->keys, keysize, srcbucket->len) + )) { + goto fail; + } + + if (!copy_value( + dst, + KVADDR(dstbucket->values, valsize, dstbucket->len), + KVADDR(srcbucket->values, valsize, srcbucket->len) + )) { + destroy_key(dst, keyaddr); + goto fail; + } + } + } + +fail: + apfl_hashmap_destroy(dst); + return NULL; +} + + static void cursor_skip_empty_buckets(apfl_hashmap_cursor cur) { @@ -328,7 +401,7 @@ cursor_skip_empty_buckets(apfl_hashmap_cursor cur) } apfl_hashmap_cursor -apfl_hashmap_get_cursor(apfl_hashmap map) +apfl_hashmap_get_cursor(const apfl_hashmap map) { apfl_hashmap_cursor cursor = malloc(sizeof(struct apfl_hashmap_cursor_struct)); if (cursor != NULL) { @@ -390,7 +463,7 @@ apfl_hashmap_cursor_next(apfl_hashmap_cursor cursor) return copy( \ cursor->map, \ out, \ - KVADDR(bucket->bucketmemb, size, bucket->len) \ + KVADDR(bucket->bucketmemb, size, cursor->i) \ ); \ bool diff --git a/src/hashmap.h b/src/hashmap.h index 976047d..403bfb4 100644 --- a/src/hashmap.h +++ b/src/hashmap.h @@ -48,10 +48,13 @@ apfl_hash apfl_hash_fnv1a(const void *, size_t len); apfl_hashmap apfl_hashmap_new(struct apfl_hashmap_callbacks, size_t keysize, size_t valsize); void apfl_hashmap_delete(apfl_hashmap, const void *key); -bool apfl_hashmap_get(apfl_hashmap, const void *key, void *value); +bool apfl_hashmap_get(const apfl_hashmap, const void *key, void *value); bool apfl_hashmap_set(apfl_hashmap, const void *key, const void *value); +size_t apfl_hashmap_count(const apfl_hashmap); void apfl_hashmap_destroy(apfl_hashmap); +apfl_hashmap apfl_hashmap_copy(apfl_hashmap src); + #define apfl_hashmap_isset(m, k) (apfl_hashmap_get((m), (k), NULL)) apfl_hashmap_cursor apfl_hashmap_get_cursor(apfl_hashmap); diff --git a/src/value.c b/src/value.c index 1c94784..611724b 100644 --- a/src/value.c +++ b/src/value.c @@ -2,42 +2,153 @@ #include "apfl.h" #include "internal.h" +#include "hashmap.h" + +struct apfl_dict_data { + unsigned refcount; + apfl_hashmap map; +}; + +static apfl_hash value_hash(const struct apfl_value); + +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 -print(unsigned indent, FILE *out, struct apfl_value value) +dict_destroy_key_or_value(void *opaque, void *kv) +{ + (void)opaque; + apfl_value_deinit(kv); +} + +static bool +dict_copy_key_or_value(void *opaque, void *dest, const void *src) +{ + (void)opaque; + return apfl_value_copy(dest, *(struct apfl_value *)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) + ); +} + +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(indent, out, "nil\n"); + apfl_print_indented(first_indent, out, "nil"); return; case APFL_VALUE_BOOLEAN: - apfl_print_indented(indent, out, value.boolean ? "true\n" : "false\n"); + apfl_print_indented(first_indent, out, value.boolean ? "true" : "false"); return; case APFL_VALUE_NUMBER: - apfl_print_indented(indent, out, "%f\n", 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(indent, out, "\"" APFL_STR_FMT "\"\n", APFL_STR_FMT_ARGS(sv)); + 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(indent, out, "[]\n"); + apfl_print_indented(first_indent, out, "[]"); return; } - apfl_print_indented(indent, out, "[\n"); + 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]); + print(indent+1, out, value.list->items[i], false); + apfl_print_indented(indent, out, "\n"); } - 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; + if (!apfl_hashmap_cursor_get_key(cur, &k)) { + apfl_hashmap_cursor_destroy(cur); + return; // TODO: Handle failure in a better way + } + if (!apfl_hashmap_cursor_get_value(cur, &v)) { + apfl_hashmap_cursor_destroy(cur); + apfl_value_deinit(&k); + return; // TODO: Handle failure in a better way + } + + 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; +} + bool apfl_value_copy(struct apfl_value *dst, struct apfl_value src) { @@ -57,6 +168,11 @@ apfl_value_copy(struct apfl_value *dst, struct apfl_value src) assert(dst->list == src.list); src.list->refcount++; goto ok; + case APFL_VALUE_DICT: + assert(src.dict != NULL); + assert(dst->dict == src.dict); + src.dict->refcount++; + goto ok; } assert(false); @@ -68,7 +184,113 @@ ok: void apfl_value_print(struct apfl_value value, FILE *out) { - print(0, out, value); + print(0, out, value, false); + fprintf(out, "\n"); +} + +static bool +list_eq(const struct apfl_list *a, const struct 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; + + // TODO: It's rather ugly that we simply return false when getting key/value fails. + + if (!apfl_hashmap_cursor_get_key(cur, &key)) { + assert(false); + return false; + } + + if (!apfl_hashmap_cursor_get_value(cur, &val)) { + apfl_value_deinit(&key); + assert(false); + return false; + } + + 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_list * @@ -97,6 +319,110 @@ apfl_list_deinit(struct apfl_list *list) list->cap = 0; } +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) { + 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); + 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) { @@ -114,6 +440,10 @@ apfl_value_deinit(struct apfl_value *value) DESTROY(value->list, apfl_list_deinit); } goto ok; + case APFL_VALUE_DICT: + apfl_dict_unref(value->dict); + value->dict = NULL; + goto ok; } assert(false); @@ -121,3 +451,42 @@ apfl_value_deinit(struct apfl_value *value) ok: value->type = APFL_VALUE_NIL; } + +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; +}