800 lines
20 KiB
C
800 lines
20 KiB
C
#include <assert.h>
|
|
|
|
#include "apfl.h"
|
|
|
|
#include "alloc.h"
|
|
#include "context.h"
|
|
#include "format.h"
|
|
#include "resizable.h"
|
|
#include "hashmap.h"
|
|
#include "value.h"
|
|
|
|
#define TRY FMT_TRY
|
|
|
|
size_t
|
|
apfl_list_len(struct list_header *list)
|
|
{
|
|
return list->len;
|
|
}
|
|
|
|
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 apfl_value_hash(*(const struct apfl_value *)key);
|
|
}
|
|
|
|
static bool
|
|
dict_init_hashmap(struct apfl_allocator allocator, struct apfl_hashmap *map)
|
|
{
|
|
bool ok = apfl_hashmap_init(
|
|
map,
|
|
allocator,
|
|
(struct apfl_hashmap_callbacks) {
|
|
.keys_eq = dict_keys_eq,
|
|
.calc_hash = dict_calc_hash,
|
|
},
|
|
sizeof(struct apfl_value),
|
|
sizeof(struct apfl_value)
|
|
);
|
|
|
|
return ok;
|
|
}
|
|
|
|
struct dict_header *
|
|
apfl_dict_new(struct gc *gc)
|
|
{
|
|
struct apfl_hashmap map;
|
|
|
|
if (!dict_init_hashmap(gc->allocator, &map)) {
|
|
return NULL;
|
|
}
|
|
|
|
struct dict_header *dict = apfl_gc_new_dict(gc);
|
|
if (dict == NULL) {
|
|
apfl_hashmap_deinit(&map);
|
|
return NULL;
|
|
}
|
|
*dict = (struct dict_header) {
|
|
.map = map,
|
|
.copy_on_write = false,
|
|
};
|
|
return dict;
|
|
}
|
|
|
|
void
|
|
apfl_dict_deinit(struct dict_header *header)
|
|
{
|
|
apfl_hashmap_deinit(&header->map);
|
|
}
|
|
|
|
static struct apfl_string_view
|
|
as_string_view(struct apfl_value value)
|
|
{
|
|
switch (value.type) {
|
|
case VALUE_STRING:
|
|
return apfl_string_view_from(*value.string);
|
|
case VALUE_CONST_STRING:
|
|
return value.const_string;
|
|
default:
|
|
return (struct apfl_string_view) { .bytes = NULL, .len = 0 };
|
|
}
|
|
}
|
|
|
|
static bool
|
|
format(unsigned indent, struct apfl_format_writer w, struct apfl_value value, bool skip_first_indent)
|
|
{
|
|
TRY(apfl_format_put_indent(w, skip_first_indent ? 0 : indent));
|
|
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
TRY(apfl_format_put_string(w, "nil"));
|
|
return true;
|
|
case VALUE_BOOLEAN:
|
|
TRY(apfl_format_put_string(w, value.boolean ? "true" : "false"));
|
|
return true;
|
|
case VALUE_NUMBER:
|
|
TRY(apfl_format_put_number(w, value.number));
|
|
return true;
|
|
case VALUE_STRING:
|
|
case VALUE_CONST_STRING:
|
|
TRY(apfl_format_put_string(w, "\""));
|
|
TRY(apfl_format_put_string(w, as_string_view(value)));
|
|
TRY(apfl_format_put_string(w, "\""));
|
|
return true;
|
|
case VALUE_LIST:
|
|
if (value.list->len == 0) {
|
|
return apfl_format_put_string(w, "[]");
|
|
}
|
|
TRY(apfl_format_put_string(w, "[\n"));
|
|
for (size_t i = 0; i < value.list->len; i++) {
|
|
TRY(format(indent+1, w, value.list->items[i], false));
|
|
TRY(apfl_format_put_string(w, "\n"));
|
|
}
|
|
TRY(apfl_format_put_indent(w, indent));
|
|
TRY(apfl_format_put_string(w, "]"));
|
|
return true;
|
|
case VALUE_DICT:
|
|
if (apfl_hashmap_count(value.dict->map) == 0) {
|
|
return apfl_format_put_string(w, "[->]");
|
|
}
|
|
|
|
TRY(apfl_format_put_string(w, "[\n"));
|
|
|
|
HASHMAP_EACH(&value.dict->map, cur) {
|
|
struct apfl_value *k = apfl_hashmap_cursor_peek_key(cur);
|
|
assert(k != NULL);
|
|
struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur);
|
|
assert(v != NULL);
|
|
|
|
TRY(format(indent+1, w, *k, false));
|
|
TRY(apfl_format_put_string(w, " -> "));
|
|
TRY(format(indent+1, w, *v, true));
|
|
TRY(apfl_format_put_string(w, "\n"));
|
|
}
|
|
TRY(apfl_format_put_indent(w, indent));
|
|
TRY(apfl_format_put_string(w, "]"));
|
|
return true;
|
|
case VALUE_FUNC:
|
|
TRY(apfl_format_put_string(w, "{ ... }"));
|
|
return true;
|
|
case VALUE_CFUNC:
|
|
TRY(apfl_format_put_string(w, "{ native code }"));
|
|
return true;
|
|
case VALUE_USERDATA:
|
|
TRY(apfl_format_put_string(w, "userdata"));
|
|
return true;
|
|
}
|
|
|
|
TRY(apfl_format_put_string(w, "Unknown value ? ("));
|
|
TRY(apfl_format_put_number(w, (int)value.type));
|
|
TRY(apfl_format_put_string(w, ")"));
|
|
return true;
|
|
}
|
|
|
|
enum apfl_value_type
|
|
apfl_value_type_to_abstract_type(enum value_type type)
|
|
{
|
|
switch (type) {
|
|
case VALUE_NIL:
|
|
return APFL_VALUE_NIL;
|
|
case VALUE_BOOLEAN:
|
|
return APFL_VALUE_BOOLEAN;
|
|
case VALUE_NUMBER:
|
|
return APFL_VALUE_NUMBER;
|
|
case VALUE_STRING:
|
|
case VALUE_CONST_STRING:
|
|
return APFL_VALUE_STRING;
|
|
case VALUE_LIST:
|
|
return APFL_VALUE_LIST;
|
|
case VALUE_DICT:
|
|
return APFL_VALUE_DICT;
|
|
case VALUE_FUNC:
|
|
case VALUE_CFUNC:
|
|
return APFL_VALUE_FUNC;
|
|
case VALUE_USERDATA:
|
|
return APFL_VALUE_USERDATA;
|
|
}
|
|
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
const char *
|
|
apfl_type_name(enum apfl_value_type type)
|
|
{
|
|
switch (type) {
|
|
case APFL_VALUE_NIL:
|
|
return "nil";
|
|
case APFL_VALUE_BOOLEAN:
|
|
return "bool";
|
|
case APFL_VALUE_NUMBER:
|
|
return "number";
|
|
case APFL_VALUE_STRING:
|
|
return "string";
|
|
case APFL_VALUE_LIST:
|
|
return "list";
|
|
case APFL_VALUE_DICT:
|
|
return "dict";
|
|
case APFL_VALUE_FUNC:
|
|
return "function";
|
|
case APFL_VALUE_USERDATA:
|
|
return "userdata";
|
|
}
|
|
|
|
return "?";
|
|
}
|
|
|
|
struct function *
|
|
apfl_func_new(struct gc *gc, size_t cap, struct scope *scope)
|
|
{
|
|
struct subfunction *subfunctions = ALLOC_LIST(gc->allocator, struct subfunction, cap);
|
|
if (subfunctions == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
struct function *function = apfl_gc_new_func(gc);
|
|
if (function == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
*function = (struct function) {
|
|
.subfunctions = subfunctions,
|
|
.subfunctions_len = 0,
|
|
.subfunctions_cap = cap,
|
|
.scope = scope,
|
|
};
|
|
|
|
return function;
|
|
}
|
|
|
|
bool
|
|
apfl_func_add_subfunc(struct function *function, struct instruction_list *body, struct matcher *matcher)
|
|
{
|
|
if (function->subfunctions_len >= function->subfunctions_cap) {
|
|
return false;
|
|
}
|
|
|
|
function->subfunctions[function->subfunctions_len] = (struct subfunction) {
|
|
.body = body,
|
|
.matcher = matcher,
|
|
};
|
|
|
|
function->subfunctions_len++;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
apfl_function_deinit(struct apfl_allocator allocator, struct function *function)
|
|
{
|
|
FREE_LIST(allocator, function->subfunctions, function->subfunctions_cap);
|
|
}
|
|
|
|
struct cfunction *
|
|
apfl_cfunc_new(struct gc *gc, apfl_cfunc func, size_t nslots)
|
|
{
|
|
struct apfl_value **slots = NULL;
|
|
if (nslots > 0) {
|
|
slots = ALLOC_LIST(gc->allocator, struct apfl_value *, nslots);
|
|
if (slots == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
struct cfunction *cfunction = apfl_gc_new_cfunc(gc);
|
|
if (cfunction == NULL) {
|
|
FREE_LIST(gc->allocator, slots, nslots);
|
|
}
|
|
|
|
for (size_t i = 0; i < nslots; i++) {
|
|
slots[i] = NULL;
|
|
}
|
|
|
|
cfunction->func = func;
|
|
cfunction->slots = slots;
|
|
cfunction->slots_len = nslots;
|
|
|
|
return cfunction;
|
|
}
|
|
|
|
void
|
|
apfl_cfunction_deinit(struct apfl_allocator allocator, struct cfunction *cfunc)
|
|
{
|
|
FREE_LIST(allocator, cfunc->slots, cfunc->slots_len);
|
|
}
|
|
|
|
struct apfl_value
|
|
apfl_value_move(struct apfl_value *src)
|
|
{
|
|
struct apfl_value out = *src;
|
|
src->type = VALUE_NIL;
|
|
return out;
|
|
}
|
|
|
|
bool
|
|
apfl_value_format(struct apfl_value value, struct apfl_format_writer w)
|
|
{
|
|
return format(0, w, value, false);
|
|
}
|
|
|
|
bool
|
|
apfl_value_print(struct apfl_value value, struct apfl_format_writer w)
|
|
{
|
|
TRY(apfl_value_format(value, w));
|
|
TRY(apfl_format_put_string(w, "\n"));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
list_eq(struct list_header *a, struct list_header *b)
|
|
{
|
|
if (a == b) {
|
|
return true;
|
|
}
|
|
|
|
if (a->len != b->len) {
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
size_t total = 0;
|
|
HASHMAP_EACH(&a->map, cur) {
|
|
struct apfl_value *key = apfl_hashmap_cursor_peek_key(cur);
|
|
assert(key != NULL);
|
|
struct apfl_value *val = apfl_hashmap_cursor_peek_value(cur);
|
|
assert(val != NULL);
|
|
|
|
struct apfl_value *other_val = apfl_hashmap_peek(&b->map, key);
|
|
if (other_val == NULL) {
|
|
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)
|
|
{
|
|
switch (a.type) {
|
|
case VALUE_NIL:
|
|
return b.type == VALUE_NIL && true;
|
|
case VALUE_BOOLEAN:
|
|
return b.type == VALUE_BOOLEAN && a.boolean == b.boolean;
|
|
case VALUE_NUMBER:
|
|
return b.type == VALUE_NUMBER && a.number == b.number;
|
|
case VALUE_STRING:
|
|
case VALUE_CONST_STRING:
|
|
return (b.type == VALUE_STRING || b.type == VALUE_CONST_STRING)
|
|
&& apfl_string_eq(as_string_view(a), as_string_view(b));
|
|
case VALUE_LIST:
|
|
return b.type == VALUE_LIST && list_eq(a.list, b.list);
|
|
case VALUE_DICT:
|
|
return b.type == VALUE_DICT && dict_eq(a.dict, b.dict);
|
|
case VALUE_FUNC:
|
|
return b.type == VALUE_FUNC && a.func == b.func;
|
|
case VALUE_CFUNC:
|
|
return b.type == VALUE_CFUNC && a.cfunc == b.cfunc;
|
|
case VALUE_USERDATA:
|
|
return b.type == VALUE_USERDATA && a.userdata == b.userdata;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
#define CMP(a, b) (((a) == (b)) ? CMP_EQ : (((a) < (b)) ? CMP_LT : CMP_GT))
|
|
|
|
enum comparison_result
|
|
apfl_value_cmp(const struct apfl_value a, const struct apfl_value b)
|
|
{
|
|
switch (a.type) {
|
|
case VALUE_NIL:
|
|
if (b.type != VALUE_NIL) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP_EQ;
|
|
case VALUE_BOOLEAN:
|
|
if (b.type != VALUE_BOOLEAN) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP(a.boolean ? 1 : 0, b.boolean ? 1 : 0);
|
|
case VALUE_NUMBER:
|
|
if (b.type != VALUE_NUMBER) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP(a.number, b.number);
|
|
case VALUE_STRING:
|
|
case VALUE_CONST_STRING:
|
|
if (b.type != VALUE_STRING && b.type != VALUE_CONST_STRING) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP(apfl_string_cmp(as_string_view(a), as_string_view(b)), 0);
|
|
case VALUE_LIST:
|
|
if (b.type != VALUE_LIST) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP_UNCOMPARABLE;
|
|
case VALUE_DICT:
|
|
if (b.type != VALUE_DICT) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP_UNCOMPARABLE;
|
|
case VALUE_FUNC:
|
|
case VALUE_CFUNC:
|
|
if (b.type != VALUE_FUNC && b.type != VALUE_CFUNC) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP_UNCOMPARABLE;
|
|
case VALUE_USERDATA:
|
|
if (b.type != VALUE_USERDATA) {
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
return CMP_UNCOMPARABLE;
|
|
}
|
|
|
|
assert(false);
|
|
return CMP_INCOMPATIBLE_TYPES;
|
|
}
|
|
|
|
struct list_header *
|
|
apfl_list_new(struct gc *gc, size_t initial_cap)
|
|
{
|
|
struct apfl_value *items = NULL;
|
|
if (initial_cap > 0) {
|
|
items = ALLOC_LIST(gc->allocator, struct apfl_value, initial_cap);
|
|
if (items == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
struct list_header *list = apfl_gc_new_list(gc);
|
|
if (list == NULL) {
|
|
FREE_LIST(gc->allocator, items, initial_cap);
|
|
return NULL;
|
|
}
|
|
|
|
*list = (struct list_header) {
|
|
.items = items,
|
|
.len = 0,
|
|
.cap = initial_cap,
|
|
.copy_on_write = false,
|
|
};
|
|
return list;
|
|
}
|
|
|
|
void
|
|
apfl_list_deinit(struct apfl_allocator allocator, struct list_header *list)
|
|
{
|
|
FREE_LIST(allocator, list->items, list->cap);
|
|
}
|
|
|
|
bool
|
|
apfl_list_splice(
|
|
struct gc *gc,
|
|
struct list_header **dst_ptr,
|
|
size_t cut_start,
|
|
size_t cut_len,
|
|
const struct apfl_value *other,
|
|
size_t other_len
|
|
) {
|
|
struct list_header *dst = *dst_ptr;
|
|
|
|
if (!apfl_resizable_check_cut_args(dst->len, cut_start, cut_len)) {
|
|
return false;
|
|
}
|
|
|
|
if (!dst->copy_on_write) {
|
|
return apfl_resizable_splice(
|
|
gc->allocator,
|
|
sizeof(struct apfl_value),
|
|
(void **)&dst->items,
|
|
&dst->len,
|
|
&dst->cap,
|
|
cut_start,
|
|
cut_len,
|
|
other,
|
|
other_len
|
|
);
|
|
}
|
|
|
|
size_t len = dst->len - cut_len + other_len;
|
|
struct apfl_value *items = ALLOC_LIST(gc->allocator, struct apfl_value, len);
|
|
if (len > 0 && items == NULL) {
|
|
return false;
|
|
}
|
|
|
|
struct list_header *new_list = apfl_gc_new_list(gc);
|
|
if (new_list == NULL) {
|
|
FREE_LIST(gc->allocator, items, len);
|
|
return false;
|
|
}
|
|
|
|
// Note that we set the COW flag here, as the values now live in two places.
|
|
for (size_t i = 0; i < cut_start; i++) {
|
|
items[i] = apfl_value_set_cow_flag(dst->items[i]);
|
|
}
|
|
for (size_t i = 0; i < other_len; i++) {
|
|
items[cut_start + i] = apfl_value_set_cow_flag(other[i]);
|
|
}
|
|
for (size_t i = cut_start + cut_len; i < dst->len; i++) {
|
|
items[other_len + i - cut_len] = apfl_value_set_cow_flag(dst->items[i]);
|
|
}
|
|
|
|
*new_list = (struct list_header) {
|
|
.items = items,
|
|
.len = len,
|
|
.cap = len,
|
|
.copy_on_write = false,
|
|
};
|
|
|
|
*dst_ptr = new_list;
|
|
return true;
|
|
}
|
|
|
|
/* Returns a dictionary for editing. Will create a copy, if neccessary.
|
|
* *_dict must be known to the garbage collector!
|
|
*/
|
|
static struct dict_header *
|
|
dict_get_for_editing(struct gc *gc, struct dict_header **_dict)
|
|
{
|
|
struct dict_header *dict = *_dict;
|
|
if (!dict->copy_on_write) {
|
|
return dict;
|
|
}
|
|
|
|
struct dict_header copy = {.copy_on_write = false};
|
|
if (!apfl_hashmap_copy(©.map, dict->map)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Set the COW flags of all keys and values in the copy.
|
|
HASHMAP_EACH(©.map, cur) {
|
|
struct apfl_value *item;
|
|
|
|
item = apfl_hashmap_cursor_peek_key(cur);
|
|
assert(item != NULL);
|
|
*item = apfl_value_set_cow_flag(*item);
|
|
|
|
item = apfl_hashmap_cursor_peek_value(cur);
|
|
assert(item != NULL);
|
|
*item = apfl_value_set_cow_flag(*item);
|
|
}
|
|
|
|
dict = apfl_gc_new_dict(gc);
|
|
if (dict == NULL) {
|
|
apfl_hashmap_deinit(©.map);
|
|
return NULL;
|
|
}
|
|
|
|
*dict = copy;
|
|
*_dict = dict;
|
|
|
|
return dict;
|
|
}
|
|
|
|
/* Set a key-value pair in a raw dictionary.
|
|
* *_dict, k and v must all be known to the garbage collector!
|
|
*/
|
|
bool
|
|
apfl_dict_set_raw(
|
|
struct gc *gc,
|
|
struct dict_header **_dict,
|
|
struct apfl_value k,
|
|
struct apfl_value v
|
|
) {
|
|
struct dict_header *dict = dict_get_for_editing(gc, _dict);
|
|
if (dict == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return apfl_hashmap_set(&dict->map, &k, &v);
|
|
}
|
|
|
|
size_t
|
|
apfl_dict_len(struct dict_header *dict)
|
|
{
|
|
return apfl_hashmap_count(dict->map);
|
|
}
|
|
|
|
static bool
|
|
list_get_item(struct list_header *list, size_t index, struct apfl_value *out)
|
|
{
|
|
if (index >= list->len) {
|
|
return false;
|
|
}
|
|
|
|
*out = list->items[index];
|
|
return true;
|
|
}
|
|
|
|
static enum get_item_result
|
|
value_get_item_inner(struct apfl_value container, 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 list_get_item(
|
|
container.list,
|
|
(size_t)key.number,
|
|
out
|
|
)
|
|
? GET_ITEM_OK
|
|
: GET_ITEM_KEY_DOESNT_EXIST;
|
|
} else if (container.type == VALUE_DICT) {
|
|
return apfl_hashmap_get(
|
|
&container.dict->map,
|
|
&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_value container, struct apfl_value key, struct apfl_value *out)
|
|
{
|
|
enum get_item_result result = value_get_item_inner(container, key, out);
|
|
if (result == GET_ITEM_OK) {
|
|
*out = apfl_value_set_cow_flag(*out);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct apfl_value
|
|
apfl_value_set_cow_flag(struct apfl_value value)
|
|
{
|
|
switch (value.type) {
|
|
case VALUE_LIST:
|
|
value.list->copy_on_write = true;
|
|
break;
|
|
case VALUE_DICT:
|
|
value.dict->copy_on_write = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
apfl_hash
|
|
apfl_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:
|
|
return hash;
|
|
case VALUE_BOOLEAN:
|
|
return apfl_hash_fnv1a_add(&value.boolean, sizeof(bool), hash);
|
|
case VALUE_NUMBER:
|
|
return apfl_hash_fnv1a_add(&value.number, sizeof(apfl_number), hash);
|
|
case VALUE_STRING:
|
|
case VALUE_CONST_STRING:
|
|
sv = as_string_view(value);
|
|
return apfl_hash_fnv1a_add(sv.bytes, sv.len, hash);
|
|
case VALUE_LIST:
|
|
for (size_t i = 0; i < value.list->len; i++) {
|
|
apfl_hash item_hash = apfl_value_hash(value.list->items[i]);
|
|
hash = apfl_hash_fnv1a_add(&item_hash, sizeof(apfl_hash), hash);
|
|
}
|
|
return hash;
|
|
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!
|
|
return hash;
|
|
case VALUE_FUNC:
|
|
return apfl_hash_fnv1a_add(&value.func, sizeof(struct function *), hash);
|
|
case VALUE_CFUNC:
|
|
return apfl_hash_fnv1a_add(&value.cfunc, sizeof(struct cfunction *), hash);
|
|
case VALUE_USERDATA:
|
|
return apfl_hash_fnv1a_add(&value.userdata, sizeof(void *), hash);
|
|
}
|
|
|
|
assert(false);
|
|
|
|
return hash;
|
|
}
|
|
|
|
struct gc_object *
|
|
apfl_value_get_gc_object(struct apfl_value value)
|
|
{
|
|
switch (value.type) {
|
|
case VALUE_NIL:
|
|
case VALUE_BOOLEAN:
|
|
case VALUE_NUMBER:
|
|
case VALUE_CONST_STRING:
|
|
case VALUE_USERDATA:
|
|
return NULL;
|
|
case VALUE_STRING:
|
|
return GC_OBJECT_FROM(value.string, GC_TYPE_STRING);
|
|
case VALUE_LIST:
|
|
return GC_OBJECT_FROM(value.list, GC_TYPE_LIST);
|
|
case VALUE_DICT:
|
|
return GC_OBJECT_FROM(value.dict, GC_TYPE_DICT);
|
|
case VALUE_FUNC:
|
|
return GC_OBJECT_FROM(value.func, GC_TYPE_FUNC);
|
|
case VALUE_CFUNC:
|
|
return GC_OBJECT_FROM(value.cfunc, GC_TYPE_CFUNC);
|
|
}
|
|
|
|
assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque)
|
|
{
|
|
struct gc_object *child = apfl_value_get_gc_object(value);
|
|
if (child != NULL) {
|
|
cb(opaque, child);
|
|
}
|
|
}
|
|
|
|
bool
|
|
apfl_value_add_as_tmproot(struct gc *gc, struct apfl_value value)
|
|
{
|
|
struct gc_object *obj = apfl_value_get_gc_object(value);
|
|
return obj == NULL ? true : apfl_gc_tmproot_add(gc, obj);
|
|
}
|
|
|
|
void
|
|
apfl_gc_list_traverse(struct list_header *list, gc_visitor cb, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < list->len; i++) {
|
|
apfl_value_visit_gc_object(list->items[i], cb, opaque);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque)
|
|
{
|
|
HASHMAP_EACH(&dict->map, cur) {
|
|
struct apfl_value *k = apfl_hashmap_cursor_peek_key(cur);
|
|
assert(k != NULL);
|
|
struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur);
|
|
assert(v != NULL);
|
|
|
|
apfl_value_visit_gc_object(*k, cb, opaque);
|
|
apfl_value_visit_gc_object(*v, cb, opaque);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_gc_func_traverse(struct function* function, gc_visitor cb, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < function->subfunctions_len; i++) {
|
|
struct subfunction *sub = &function->subfunctions[i];
|
|
cb(opaque, GC_OBJECT_FROM(sub->body, GC_TYPE_INSTRUCTIONS));
|
|
cb(opaque, GC_OBJECT_FROM(sub->matcher, GC_TYPE_MATCHER));
|
|
}
|
|
cb(opaque, GC_OBJECT_FROM(function->scope, GC_TYPE_SCOPE));
|
|
}
|
|
|
|
void
|
|
apfl_gc_cfunc_traverse(struct cfunction* cfunc, gc_visitor cb, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < cfunc->slots_len; i++) {
|
|
if (cfunc->slots[i] != NULL) {
|
|
cb(opaque, GC_OBJECT_FROM(cfunc->slots[i], GC_TYPE_VAR));
|
|
}
|
|
}
|
|
}
|