This let's us get rid of that awkward hashmap in the GC that was used as a set, makes determining the roots more flexible and now gc_init can't even fail any more, as there are no allocations happening here any more :)
675 lines
16 KiB
C
675 lines
16 KiB
C
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "apfl.h"
|
|
#include "alloc.h"
|
|
#include "context.h"
|
|
#include "gc.h"
|
|
#include "hashmap.h"
|
|
#include "resizable.h"
|
|
#include "strings.h"
|
|
#include "value.h"
|
|
|
|
static bool try_push_const_string(apfl_ctx ctx, const char *string);
|
|
|
|
APFL_NORETURN static void
|
|
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
|
|
{
|
|
(void)ctx;
|
|
(void)result;
|
|
(void)with_error_on_stack;
|
|
fprintf(stderr, "panic!\n");
|
|
// TODO: more details
|
|
abort();
|
|
}
|
|
|
|
#define RESULT_OFF_FOR_LONGJMP 1
|
|
|
|
enum apfl_result
|
|
apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *with_error_on_stack)
|
|
{
|
|
struct error_handler *prev_handler = ctx->error_handler;
|
|
|
|
size_t stack_len = ctx->stack->len;
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
struct error_handler handler;
|
|
ctx->error_handler = &handler;
|
|
|
|
enum apfl_result result = APFL_RESULT_OK;
|
|
int rv = setjmp(handler.jump);
|
|
if (rv == 0) {
|
|
cb(ctx, opaque);
|
|
} else {
|
|
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
|
assert(result != APFL_RESULT_OK);
|
|
|
|
struct apfl_value err;
|
|
if ((*with_error_on_stack = handler.with_error_on_stack)) {
|
|
if (!apfl_stack_pop(ctx, &err, -1)) {
|
|
*with_error_on_stack = false;
|
|
// TODO: Indicate error during error handling
|
|
}
|
|
}
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
assert(stack_len <= ctx->stack->cap);
|
|
ctx->stack->len = stack_len;
|
|
|
|
if (*with_error_on_stack) {
|
|
if (!apfl_stack_push(ctx, err)) {
|
|
*with_error_on_stack = false;
|
|
// TODO: Indicate error during error handling
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx->error_handler = prev_handler;
|
|
|
|
return result;
|
|
}
|
|
|
|
APFL_NORETURN static void
|
|
raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type)
|
|
{
|
|
assert(type != APFL_RESULT_OK);
|
|
|
|
if (ctx->error_handler != NULL) {
|
|
ctx->error_handler->with_error_on_stack = with_error_on_stack;
|
|
longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP);
|
|
}
|
|
|
|
if (ctx->panic_callback != NULL) {
|
|
ctx->panic_callback(ctx, with_error_on_stack, ctx->panic_callback_data);
|
|
}
|
|
|
|
panic(ctx, with_error_on_stack, type);
|
|
}
|
|
|
|
APFL_NORETURN void
|
|
apfl_raise_error_with_type(apfl_ctx ctx, apfl_stackidx idx, enum apfl_result type)
|
|
{
|
|
bool ok = apfl_stack_move_to_top(ctx, idx);
|
|
raise_error(ctx, ok, type);
|
|
}
|
|
|
|
APFL_NORETURN void
|
|
apfl_raise_const_error(apfl_ctx ctx, enum apfl_result type, const char *message)
|
|
{
|
|
raise_error(ctx, try_push_const_string(ctx, message), type);
|
|
}
|
|
|
|
APFL_NORETURN void
|
|
apfl_raise_alloc_error(apfl_ctx ctx)
|
|
{
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR_FATAL, apfl_messages.could_not_alloc_mem);
|
|
}
|
|
|
|
APFL_NORETURN void
|
|
apfl_raise_invalid_stackidx(apfl_ctx ctx)
|
|
{
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_stack_index);
|
|
}
|
|
|
|
APFL_NORETURN void
|
|
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
|
|
{
|
|
enum apfl_result errtype = APFL_RESULT_ERR;
|
|
if (apfl_error_is_fatal_type(error.type)) {
|
|
errtype = APFL_RESULT_ERR_FATAL;
|
|
}
|
|
|
|
const char *const_str = apfl_error_as_const_string(error);
|
|
if (const_str != NULL) {
|
|
apfl_raise_const_error(ctx, errtype, const_str);
|
|
}
|
|
|
|
struct apfl_string string;
|
|
if (!apfl_error_as_string(error, ctx->gc.allocator, &string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
apfl_raise_error_with_type(ctx, -1, errtype);
|
|
}
|
|
|
|
void
|
|
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
|
|
{
|
|
ctx->panic_callback = cb;
|
|
ctx->panic_callback_data = opaque;
|
|
}
|
|
|
|
static struct stack *
|
|
stack_new(struct gc *gc)
|
|
{
|
|
struct stack *stack = apfl_gc_new_stack(gc);
|
|
if (stack == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
*stack = (struct stack) {
|
|
.items = NULL,
|
|
.len = 0,
|
|
.cap = 0,
|
|
};
|
|
|
|
return stack;
|
|
}
|
|
|
|
void
|
|
apfl_stack_deinit(struct apfl_allocator allocator, struct stack *stack)
|
|
{
|
|
FREE_LIST(allocator, stack->items, stack->cap);
|
|
}
|
|
|
|
void
|
|
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
|
|
{
|
|
if (!apfl_stack_push(ctx, value)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
}
|
|
|
|
bool
|
|
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
|
|
{
|
|
return apfl_resizable_append(
|
|
ctx->gc.allocator,
|
|
sizeof(struct apfl_value),
|
|
(void **)&ctx->stack->items,
|
|
&ctx->stack->len,
|
|
&ctx->stack->cap,
|
|
&value,
|
|
1
|
|
);
|
|
}
|
|
|
|
bool
|
|
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
|
|
{
|
|
if (*index < 0) {
|
|
if ((size_t)-*index > ctx->stack->len) {
|
|
return false;
|
|
}
|
|
*index = ctx->stack->len + *index;
|
|
} else if ((size_t)*index >= ctx->stack->len) {
|
|
return false;
|
|
}
|
|
|
|
assert(0 <= *index && (size_t)*index < ctx->stack->len);
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
cmp_stackidx(const void *_a, const void *_b)
|
|
{
|
|
const apfl_stackidx *a = _a;
|
|
const apfl_stackidx *b = _b;
|
|
return *a - *b;
|
|
}
|
|
|
|
bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
|
{
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (!apfl_stack_check_index(ctx, &indices[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
|
|
|
|
for (size_t i = count; i-- > 0; ) {
|
|
// Will not fail, as we've already checked the indices
|
|
assert(apfl_resizable_cut_without_resize(
|
|
sizeof(struct apfl_value),
|
|
(void **)&ctx->stack->items,
|
|
&ctx->stack->len,
|
|
indices[i],
|
|
1
|
|
));
|
|
}
|
|
|
|
// TODO: Shrink stack
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
{
|
|
if (!apfl_stack_check_index(ctx, &index)) {
|
|
return false;
|
|
}
|
|
|
|
*value = ctx->stack->items[index];
|
|
|
|
assert(apfl_resizable_splice(
|
|
ctx->gc.allocator,
|
|
sizeof(struct apfl_value),
|
|
(void **)ctx->stack,
|
|
&ctx->stack->len,
|
|
&ctx->stack->cap,
|
|
index,
|
|
1,
|
|
NULL,
|
|
0
|
|
));
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct apfl_value *
|
|
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
if (!apfl_stack_check_index(ctx, &index)) {
|
|
return NULL;
|
|
}
|
|
|
|
return &ctx->stack->items[index];
|
|
}
|
|
|
|
static bool
|
|
stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
|
|
{
|
|
if (!apfl_stack_check_index(ctx, index)) {
|
|
return false;
|
|
}
|
|
|
|
*value = ctx->stack->items[*index];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
{
|
|
return stack_get_and_adjust_index(ctx, value, &index);
|
|
}
|
|
|
|
struct apfl_value *
|
|
apfl_stack_push_placeholder(apfl_ctx ctx)
|
|
{
|
|
if (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
|
|
return NULL;
|
|
}
|
|
|
|
return stack_get_pointer(ctx, -1);
|
|
}
|
|
|
|
bool
|
|
apfl_move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
|
{
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
if (value == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
|
return false;
|
|
}
|
|
|
|
value->type = VALUE_STRING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value value;
|
|
return apfl_stack_pop(ctx, &value, index);
|
|
}
|
|
|
|
bool
|
|
apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
|
{
|
|
struct apfl_value val;
|
|
if (!stack_get_and_adjust_index(ctx, &val, &index)) {
|
|
return false;
|
|
}
|
|
|
|
size_t absindex = (size_t)index;
|
|
|
|
// If we're here, index is an absolute address and is guaranteed to be < len
|
|
assert(ctx->stack->len >= absindex+1);
|
|
|
|
memmove(
|
|
&ctx->stack->items[absindex + 1],
|
|
&ctx->stack->items[absindex],
|
|
ctx->stack->len - absindex - 1
|
|
);
|
|
ctx->stack->items[ctx->stack->len-1] = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
apfl_stack_clear(apfl_ctx ctx)
|
|
{
|
|
ctx->stack->len = 0;
|
|
}
|
|
|
|
static void
|
|
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
|
|
{
|
|
apfl_ctx ctx = own_opaque;
|
|
|
|
if (ctx->scope != NULL) {
|
|
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->scope, GC_TYPE_SCOPE));
|
|
}
|
|
|
|
if (ctx->stack != NULL) {
|
|
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->stack, GC_TYPE_STACK));
|
|
}
|
|
}
|
|
|
|
apfl_ctx
|
|
apfl_ctx_new(struct apfl_allocator base_allocator)
|
|
{
|
|
apfl_ctx ctx = ALLOC_OBJ(base_allocator, struct apfl_ctx_data);
|
|
if (ctx == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
apfl_gc_init(&ctx->gc, base_allocator, get_roots, ctx);
|
|
|
|
if ((ctx->scope = apfl_scope_new(&ctx->gc)) == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if ((ctx->stack = stack_new(&ctx->gc)) == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
ctx->error_handler = NULL;
|
|
ctx->panic_callback = NULL;
|
|
ctx->panic_callback_data = NULL;
|
|
|
|
return ctx;
|
|
|
|
error:
|
|
apfl_ctx_destroy(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
apfl_ctx_destroy(apfl_ctx ctx)
|
|
{
|
|
if (ctx == NULL) {
|
|
return;
|
|
}
|
|
|
|
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
|
|
|
|
apfl_gc_full(&ctx->gc);
|
|
apfl_gc_deinit(&ctx->gc);
|
|
|
|
FREE_OBJ(base_allocator, ctx);
|
|
}
|
|
|
|
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
|
|
if (value == NULL) { \
|
|
apfl_raise_alloc_error(ctx); \
|
|
} \
|
|
\
|
|
struct apfl_value new_value = {.type = TYPE}; \
|
|
if ((new_value.MEMB = NEW) == NULL) { \
|
|
assert(apfl_stack_drop(ctx, -1)); \
|
|
apfl_raise_alloc_error(ctx); \
|
|
} \
|
|
\
|
|
*value = new_value;
|
|
|
|
void
|
|
apfl_push_nil(apfl_ctx ctx)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_NIL,
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_bool(apfl_ctx ctx, bool b)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_BOOLEAN,
|
|
.boolean = b,
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
|
{
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_NUMBER,
|
|
.number = num,
|
|
});
|
|
}
|
|
|
|
static struct apfl_string *
|
|
new_copied_string(struct gc *gc, struct apfl_string_view sv)
|
|
{
|
|
struct apfl_string s = apfl_string_blank();
|
|
if (!apfl_string_copy(gc->allocator, &s, sv)) {
|
|
return NULL;
|
|
}
|
|
|
|
struct apfl_string *out = apfl_string_move_into_new_gc_string(gc, &s);
|
|
if (out == NULL) {
|
|
apfl_string_deinit(gc->allocator, &s);
|
|
return NULL;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
void
|
|
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_STRING,
|
|
string,
|
|
new_copied_string(&ctx->gc, sv)
|
|
)
|
|
}
|
|
|
|
static bool
|
|
try_push_const_string(apfl_ctx ctx, const char *string)
|
|
{
|
|
return apfl_stack_push(ctx, (struct apfl_value) {
|
|
.type = VALUE_CONST_STRING,
|
|
.const_string = apfl_string_view_from(string),
|
|
});
|
|
}
|
|
|
|
void
|
|
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
|
{
|
|
if (!try_push_const_string(ctx, string)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_LIST,
|
|
list,
|
|
apfl_list_new(&ctx->gc, initial_cap)
|
|
)
|
|
}
|
|
|
|
void
|
|
apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_index)
|
|
{
|
|
struct apfl_value *list_val = stack_get_pointer(ctx, list_index);
|
|
if (list_val == NULL) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (list_val->type != VALUE_LIST) {
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
|
}
|
|
|
|
struct apfl_value value;
|
|
if (!apfl_stack_get(ctx, &value, value_index)) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (!apfl_list_splice(
|
|
&ctx->gc,
|
|
&list_val->list,
|
|
list_val->list->len,
|
|
0,
|
|
&value,
|
|
1
|
|
)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
|
}
|
|
|
|
void
|
|
apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_index)
|
|
{
|
|
struct apfl_value *dst_val = stack_get_pointer(ctx, dst_index);
|
|
if (dst_val == NULL) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (dst_val->type != VALUE_LIST) {
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
|
}
|
|
|
|
struct apfl_value src_val;
|
|
if (!apfl_stack_get(ctx, &src_val, src_index)) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (src_val.type != VALUE_LIST) {
|
|
assert(apfl_stack_drop(ctx, src_index));
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
|
}
|
|
|
|
if (!apfl_list_splice(
|
|
&ctx->gc,
|
|
&dst_val->list,
|
|
dst_val->list->len,
|
|
0,
|
|
src_val.list->items,
|
|
src_val.list->len
|
|
)) {
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
|
}
|
|
|
|
void
|
|
apfl_dict_create(apfl_ctx ctx)
|
|
{
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
ctx,
|
|
VALUE_DICT,
|
|
dict,
|
|
apfl_dict_new(&ctx->gc)
|
|
)
|
|
}
|
|
|
|
void
|
|
apfl_dict_set(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx dict_index,
|
|
apfl_stackidx k_index,
|
|
apfl_stackidx v_index
|
|
) {
|
|
struct apfl_value k;
|
|
struct apfl_value v;
|
|
struct apfl_value *dict_value;
|
|
|
|
if (
|
|
!apfl_stack_get(ctx, &k, k_index)
|
|
|| !apfl_stack_get(ctx, &v, v_index)
|
|
|| (dict_value = stack_get_pointer(ctx, dict_index)) == NULL
|
|
) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
if (dict_value->type != VALUE_DICT) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict);
|
|
}
|
|
|
|
if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
}
|
|
|
|
void
|
|
apfl_get_member(
|
|
apfl_ctx ctx,
|
|
apfl_stackidx container_index,
|
|
apfl_stackidx k_index
|
|
) {
|
|
struct apfl_value container;
|
|
struct apfl_value k;
|
|
if (
|
|
!stack_get_and_adjust_index(ctx, &container, &container_index)
|
|
|| !stack_get_and_adjust_index(ctx, &k, &k_index)
|
|
) {
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
}
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
if (value == NULL) {
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
apfl_raise_alloc_error(ctx);
|
|
}
|
|
|
|
enum get_item_result result = apfl_value_get_item(container, k, value);
|
|
if (result != GET_ITEM_OK) {
|
|
assert(apfl_stack_drop(ctx, -1));
|
|
}
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
|
|
switch (result) {
|
|
case GET_ITEM_OK:
|
|
break;
|
|
case GET_ITEM_KEY_DOESNT_EXIST:
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
|
|
break;
|
|
case GET_ITEM_NOT_A_CONTAINER:
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_is_not_a_container);
|
|
break;
|
|
case GET_ITEM_WRONG_KEY_TYPE:
|
|
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_key_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
apfl_gc_stack_traverse(struct stack *stack, gc_visitor visitor, void *opaque)
|
|
{
|
|
for (size_t i = 0; i < stack->len; i++) {
|
|
struct gc_object *object = apfl_value_get_gc_object(stack->items[i]);
|
|
if (object != NULL) {
|
|
visitor(opaque, object);
|
|
}
|
|
}
|
|
}
|