668 lines
16 KiB
C
668 lines
16 KiB
C
#include <assert.h>
|
|
|
|
#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 VALUE_NIL:
|
|
apfl_print_indented(first_indent, out, "nil");
|
|
return;
|
|
case VALUE_BOOLEAN:
|
|
apfl_print_indented(first_indent, out, value.boolean ? "true" : "false");
|
|
return;
|
|
case VALUE_NUMBER:
|
|
apfl_print_indented(first_indent, out, "%f", value.number);
|
|
return;
|
|
case 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 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 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 = VALUE_NIL;
|
|
return out;
|
|
}
|
|
|
|
struct apfl_value
|
|
apfl_value_incref(struct apfl_value value)
|
|
{
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
case VALUE_BOOLEAN:
|
|
case VALUE_NUMBER:
|
|
// Nothing to do
|
|
return value;
|
|
case VALUE_STRING:
|
|
value.string = apfl_refcounted_string_incref(value.string);
|
|
return value;
|
|
case VALUE_LIST:
|
|
value.list = apfl_list_incref(value.list);
|
|
return value;
|
|
case 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 VALUE_NIL:
|
|
return true;
|
|
case VALUE_BOOLEAN:
|
|
return a.boolean == b.boolean;
|
|
case VALUE_NUMBER:
|
|
return a.number == b.number;
|
|
case VALUE_STRING:
|
|
return apfl_string_eq(a.string, b.string);
|
|
case VALUE_LIST:
|
|
return list_eq(a.list, b.list);
|
|
case 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 VALUE_NIL:
|
|
case VALUE_BOOLEAN:
|
|
case VALUE_NUMBER:
|
|
goto ok;
|
|
case VALUE_STRING:
|
|
apfl_refcounted_string_unref(allocator, value->string);
|
|
value->string = NULL;
|
|
goto ok;
|
|
case VALUE_LIST:
|
|
apfl_list_unref(allocator, value->list);
|
|
value->list = NULL;
|
|
goto ok;
|
|
case VALUE_DICT:
|
|
apfl_dict_unref(allocator, value->dict);
|
|
value->dict = NULL;
|
|
goto ok;
|
|
}
|
|
|
|
assert(false);
|
|
|
|
ok:
|
|
value->type = 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 == VALUE_LIST) {
|
|
if (key.type != 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 == 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 value_type));
|
|
|
|
struct apfl_string_view sv;
|
|
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
goto ok;
|
|
case VALUE_BOOLEAN:
|
|
hash = apfl_hash_fnv1a_add(&value.boolean, sizeof(bool), hash);
|
|
goto ok;
|
|
case VALUE_NUMBER:
|
|
hash = apfl_hash_fnv1a_add(&value.number, sizeof(apfl_number), hash);
|
|
goto ok;
|
|
case VALUE_STRING:
|
|
sv = apfl_string_view_from(value.string);
|
|
hash = apfl_hash_fnv1a_add(sv.bytes, sv.len, hash);
|
|
goto ok;
|
|
case 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 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;
|
|
}
|