apfl/src/value.c

702 lines
16 KiB
C
Raw Normal View History

#include <assert.h>
#include "apfl.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;
};
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;
}
size_t
apfl_list_len(apfl_list list)
{
return list->len;
}
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 first need to create a shallow copy of the elements
if (list->len != 0) {
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_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(&key);
return ok;
}
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;
}