Implement evaluating dictionary literals
We can now have dictionaries with keys and values of arbitrary types. Very cool! :)
This commit is contained in:
parent
8b5d881ab9
commit
f3d0bbed17
6 changed files with 549 additions and 25 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
26
src/apfl.h
26
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,
|
||||
|
|
|
|||
57
src/eval.c
57
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
389
src/value.c
389
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue