apfl/src/context.c
Laria Carolin Chabowski fa4ed7bf83 Abstract away stdout
Instead of using stdout directly, we're now using a configurable
apfl_format_writer.
2022-10-30 21:21:40 +01:00

1310 lines
32 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 "globals.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);
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
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();
}
enum apfl_result
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
{
struct error_handler *prev_handler = ctx->error_handler;
size_t callstack_len = ctx->call_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) {
apfl_call(ctx, func, args);
} 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(callstack_len <= ctx->call_stack.cap);
for (size_t i = callstack_len; i < ctx->call_stack.len; i++) {
apfl_call_stack_entry_deinit(ctx->gc.allocator, &ctx->call_stack.items[i]);
}
assert(
// Shrinking should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void **)&ctx->call_stack.items,
&ctx->call_stack.len,
&ctx->call_stack.cap,
callstack_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 = current_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;
}
struct stack
apfl_stack_new(void)
{
return (struct stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static struct stack *
current_value_stack(apfl_ctx ctx)
{
struct call_stack_entry *csentry = apfl_call_stack_cur_entry(ctx);
return csentry != NULL
? &csentry->stack
: &ctx->toplevel_stack;
}
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)
{
struct stack *stack = current_value_stack(ctx);
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)&stack->items,
&stack->len,
&stack->cap,
&value,
1
);
}
static bool
stack_check_index(struct stack *stack, apfl_stackidx *index)
{
if (*index < 0) {
if ((size_t)-*index > stack->len) {
return false;
}
*index = stack->len + *index;
} else if ((size_t)*index >= stack->len) {
return false;
}
assert(0 <= *index && (size_t)*index < stack->len);
return true;
}
bool
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
{
return stack_check_index(current_value_stack(ctx), index);
}
bool
apfl_stack_has_index(apfl_ctx ctx, apfl_stackidx index)
{
return apfl_stack_check_index(ctx, &index);
}
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)
{
struct stack *stack = current_value_stack(ctx);
for (size_t i = 0; i < count; i++) {
if (!stack_check_index(stack, &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 **)&stack->items,
&stack->len,
indices[i],
1
));
}
// TODO: Shrink stack
return true;
}
bool
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
struct stack *stack = current_value_stack(ctx);
if (!stack_check_index(stack, &index)) {
return false;
}
*value = stack->items[index];
assert(apfl_resizable_splice(
ctx->gc.allocator,
sizeof(struct apfl_value),
(void **)&stack->items,
&stack->len,
&stack->cap,
index,
1,
NULL,
0
));
return true;
}
struct apfl_value
apfl_stack_must_pop(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, index)) {
apfl_raise_invalid_stackidx(ctx);
}
return value;
}
static struct apfl_value *
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
{
struct stack *stack = current_value_stack(ctx);
if (!stack_check_index(stack, &index)) {
return NULL;
}
return &stack->items[index];
}
static bool
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index)
{
if (!stack_check_index(stack, index)) {
return false;
}
*value = stack->items[*index];
return true;
}
static bool
current_stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
{
return stack_get_and_adjust_index(current_value_stack(ctx), value, index);
}
bool
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
{
return current_stack_get_and_adjust_index(ctx, value, &index);
}
struct apfl_value
apfl_stack_must_get(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value;
if (!apfl_stack_get(ctx, &value, index)) {
apfl_raise_invalid_stackidx(ctx);
}
return value;
}
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);
}
void
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
{
if (!apfl_stack_drop(ctx, index)) {
apfl_raise_invalid_stackidx(ctx);
}
}
static bool
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
{
struct stack *stack = current_value_stack(ctx);
struct apfl_value val;
if (!stack_get_and_adjust_index(stack, &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(stack->len >= absindex+1);
memmove(
&stack->items[absindex + 1],
&stack->items[absindex],
stack->len - absindex - 1
);
stack->items[stack->len-1] = val;
return true;
}
void
apfl_stack_clear(apfl_ctx ctx)
{
struct stack *stack = current_value_stack(ctx);
stack->len = 0;
}
static void
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);
}
}
}
static void
visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque)
{
if (scope != NULL) {
visitor(opaque, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE));
}
}
static void
gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, void *opaque)
{
stack_traverse(cse.stack, visitor, opaque);
switch (cse.type) {
case CSE_FUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.func.instructions, GC_TYPE_INSTRUCTIONS)
);
visit_nullable_scope(cse.func.scope, visitor, opaque);
visit_nullable_scope(cse.func.closure_scope, visitor, opaque);
if (cse.func.matcher != NULL) {
visitor(
opaque,
GC_OBJECT_FROM(cse.func.matcher, GC_TYPE_MATCHER)
);
}
break;
case CSE_CFUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
);
break;
case CSE_MATCHER:
visitor(
opaque,
GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER)
);
break;
case CSE_FUNCTION_DISPATCH:
visitor(
opaque,
GC_OBJECT_FROM(cse.func_dispatch.function, GC_TYPE_FUNC)
);
break;
}
}
static void
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
{
apfl_ctx ctx = own_opaque;
if (ctx->globals != NULL) {
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE));
}
stack_traverse(ctx->toplevel_stack, visitor, visitor_opaque);
for (size_t i = 0; i < ctx->call_stack.len; i++) {
gc_traverse_call_stack_entry(ctx->call_stack.items[i], visitor, visitor_opaque);
}
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
apfl_iterative_runner_visit_gc_objects(ctx->iterative_runners.items[i], visitor, visitor_opaque);
}
}
static struct call_stack
call_stack_new(void)
{
return (struct call_stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static void
deinit_stack(struct apfl_allocator allocator, struct stack *stack)
{
FREE_LIST(allocator, stack->items, stack->cap);
*stack = apfl_stack_new();
}
void
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
{
deinit_stack(allocator, &entry->stack);
switch (entry->type) {
case CSE_FUNCTION:
case CSE_CFUNCTION:
case CSE_FUNCTION_DISPATCH:
break;
case CSE_MATCHER:
FREE_LIST(allocator, entry->matcher.matcher_stack, entry->matcher.matcher_stack_cap);
break;
}
}
struct call_stack_entry *
apfl_call_stack_cur_entry(apfl_ctx ctx)
{
return ctx->call_stack.len == 0
? NULL
: &ctx->call_stack.items[ctx->call_stack.len - 1];
}
static struct iterative_runners_list
iterative_runners_list_new(void)
{
return (struct iterative_runners_list) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static bool
init_globals(apfl_ctx ctx)
{
struct gc *gc = &ctx->gc;
size_t tmproots = apfl_gc_tmproots_begin(gc);
for (
const struct global_def *global = apfl_globals();
global->name != NULL && global->func != NULL;
global++
) {
struct cfunction *cfunc = apfl_cfunc_new(gc, global->func, 0);
if (cfunc == NULL) {
goto error;
}
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(cfunc, GC_TYPE_CFUNC))) {
goto error;
}
// TODO: It's silly that we need to copy the name into a new gc string.
// It should be possible for the scope to also have a const string
// as a name.
struct apfl_string *name = new_copied_string(gc, apfl_string_view_from(global->name));
if (name == NULL) {
goto error;
}
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) {
goto error;
}
if (!apfl_scope_set(gc, ctx->globals, name, (struct apfl_value) {
.type = VALUE_CFUNC,
.cfunc = cfunc,
})) {
goto error;
}
apfl_gc_tmproots_restore(gc, tmproots);
}
return true;
error:
apfl_gc_tmproots_restore(gc, tmproots);
return false;
}
apfl_ctx
apfl_ctx_new(struct apfl_config config)
{
apfl_ctx ctx = ALLOC_OBJ(config.allocator, struct apfl_ctx_data);
if (ctx == NULL) {
return NULL;
}
// It's important that we initialize all these before initializing the
// garbage collector, otherwise it might try to follow invalid pointers.
ctx->error_handler = NULL;
ctx->panic_callback = NULL;
ctx->panic_callback_data = NULL;
ctx->toplevel_stack = apfl_stack_new();
ctx->call_stack = call_stack_new();
ctx->globals = NULL;
ctx->iterative_runners = iterative_runners_list_new();
apfl_gc_init(&ctx->gc, config.allocator, get_roots, ctx);
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
goto error;
}
ctx->output_writer = config.output_writer;
if (!init_globals(ctx)) {
goto error;
}
return ctx;
error:
apfl_ctx_destroy(ctx);
return NULL;
}
void
apfl_ctx_destroy(apfl_ctx ctx)
{
if (ctx == NULL) {
return;
}
deinit_stack(ctx->gc.allocator, &ctx->toplevel_stack);
ctx->toplevel_stack = apfl_stack_new();
DEINIT_CAP_LIST(
ctx->gc.allocator,
ctx->call_stack.items,
ctx->call_stack.len,
ctx->call_stack.cap,
apfl_call_stack_entry_deinit
);
ctx->call_stack = call_stack_new();
FREE_LIST(ctx->gc.allocator, ctx->iterative_runners.items, ctx->iterative_runners.cap);
ctx->iterative_runners = iterative_runners_list_new();
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
apfl_gc_full(&ctx->gc);
apfl_gc_deinit(&ctx->gc);
FREE_OBJ(base_allocator, ctx);
}
static bool
find_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner, size_t *index)
{
// It should be very uncommon that there are a lot of iterative runners
// existing on the same apfl_ctx at the same time, so a linear scan should
// be good enough :)
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
if (ctx->iterative_runners.items[i] == runner) {
if (index != NULL) {
*index = i;
}
return true;
}
}
return false;
}
struct iterative_runner_tmproot_data {
struct gc *gc;
bool ok;
};
static void
ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object)
{
struct iterative_runner_tmproot_data *data = opaque;
if (!data->ok) {
return;
}
data->ok = apfl_gc_tmproot_add(data->gc, object);
}
static bool
ctx_register_iterative_runner_inner(apfl_ctx ctx, apfl_iterative_runner runner)
{
struct iterative_runner_tmproot_data data = {
.gc = &ctx->gc,
.ok = true
};
apfl_iterative_runner_visit_gc_objects(runner, ctx_register_iterative_runner_tmproot, &data);
if (!data.ok) {
return false;
}
if (find_iterative_runner(ctx, runner, NULL)) {
return true;
}
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(apfl_iterative_runner),
(void **)&ctx->iterative_runners.items,
&ctx->iterative_runners.len,
&ctx->iterative_runners.cap,
&runner,
1
);
}
bool
apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
bool out = ctx_register_iterative_runner_inner(ctx, runner);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
return out;
}
void
apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
{
size_t i;
if (!find_iterative_runner(ctx, runner, &i)) {
return;
}
assert(
// We're only removing elements, the buffer should not grow,
// therefore there should be no allocation errors
apfl_resizable_splice(
ctx->gc.allocator,
sizeof(apfl_iterative_runner),
(void **)&ctx->iterative_runners.items,
&ctx->iterative_runners.len,
&ctx->iterative_runners.cap,
i,
1,
NULL,
0
)
);
}
#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 = apfl_stack_must_get(ctx, value_index);
if (!apfl_list_splice(
&ctx->gc,
&list_val->list,
list_val->list->len,
0,
&value,
1
)) {
assert(apfl_stack_drop(ctx, value_index));
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 = apfl_stack_must_get(ctx, src_index);
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 (
!current_stack_get_and_adjust_index(ctx, &container, &container_index)
|| !current_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_get_list_member_by_index(
apfl_ctx ctx,
apfl_stackidx list_index,
size_t index
) {
struct apfl_value list = apfl_stack_must_get(ctx, list_index);
if (list.type != VALUE_LIST) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
if (index >= list.list->len) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
}
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
if (value == NULL) {
apfl_raise_alloc_error(ctx);
}
*value = apfl_value_set_cow_flag(list.list->items[index]);
return;
}
size_t
apfl_len(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_get(ctx, index);
switch (value.type) {
case VALUE_NIL:
case VALUE_BOOLEAN:
case VALUE_NUMBER:
case VALUE_FUNC:
case VALUE_CFUNC:
case VALUE_USERDATA:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
return 0;
case VALUE_STRING:
return value.string->len;
case VALUE_CONST_STRING:
return value.const_string.len;
case VALUE_LIST:
return apfl_list_len(value.list);
case VALUE_DICT:
return apfl_dict_len(value.dict);
}
assert(false);
return 0;
}
struct apfl_string_view
apfl_get_string(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_get(ctx, index);
switch (value.type) {
case VALUE_NIL:
case VALUE_BOOLEAN:
case VALUE_NUMBER:
case VALUE_FUNC:
case VALUE_CFUNC:
case VALUE_USERDATA:
case VALUE_LIST:
case VALUE_DICT:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
goto error;
case VALUE_STRING:
return apfl_string_view_from(*value.string);
case VALUE_CONST_STRING:
return value.const_string;
}
error:
assert(false);
return (struct apfl_string_view) {.bytes = NULL, .len = 0};
}
enum apfl_value_type
apfl_get_type(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_get(ctx, index);
return apfl_value_type_to_abstract_type(value.type);
}
bool
apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_get(ctx, index);
switch (value.type) {
case VALUE_NIL:
return false;
case VALUE_BOOLEAN:
return value.boolean;
default:
return true;
}
}
apfl_number
apfl_get_number(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_pop(ctx, index);
if (value.type == VALUE_NUMBER) {
return value.number;
}
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
}
bool
apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
{
struct apfl_value a = apfl_stack_must_get(ctx, _a);
struct apfl_value b = apfl_stack_must_get(ctx, _b);
bool eq = apfl_value_eq(a, b);
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}));
return eq;
}
static struct scope *
closure_scope_for_func_inner(apfl_ctx ctx)
{
struct scope *out = apfl_scope_new(&ctx->gc);
if (out == NULL) {
return NULL;
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(out, GC_TYPE_SCOPE))) {
return NULL;
}
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL || entry->type != CSE_FUNCTION) {
return out;
}
// The order is important here: by merging entry->scope last, we make sure a
// variable from the current scope shadows a variable from the closure scope
if (entry->func.closure_scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.closure_scope)) {
return NULL;
}
}
if (entry->func.scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.scope)) {
return NULL;
}
}
return out;
}
struct scope *
apfl_closure_scope_for_func(apfl_ctx ctx)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct scope *out = closure_scope_for_func_inner(ctx);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
return out;
}
void
apfl_push_cfunc(apfl_ctx ctx, apfl_cfunc cfunc, size_t nslots)
{
CREATE_GC_OBJECT_VALUE_ON_STACK(
ctx,
VALUE_CFUNC,
cfunc,
apfl_cfunc_new(&ctx->gc, cfunc, nslots)
)
}
static APFL_NORETURN void
raise_no_cfunc(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_c_function);
}
static struct cfunction *
must_get_cfunc(apfl_ctx ctx, apfl_stackidx idx)
{
struct apfl_value value = apfl_stack_must_get(ctx, idx);
if (value.type != VALUE_CFUNC) {
raise_no_cfunc(ctx);
}
return value.cfunc;
}
static struct cfunction *
must_get_cfunc_self(apfl_ctx ctx)
{
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL) {
raise_no_cfunc(ctx);
}
if (entry->type != CSE_CFUNCTION) {
raise_no_cfunc(ctx);
}
return entry->cfunc.func;
}
static struct apfl_value **
cfunc_getslotvar(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
{
if (slot >= cfunction->slots_len) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_slotidx);
}
return &cfunction->slots[slot];
}
static void
cfunc_getslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
{
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
if (*var == NULL) {
apfl_push_nil(ctx);
return;
}
// The value is now in the slot and on the stack. We need to set the COW
// flag so a mutation of one copy doesn't affect the other one.
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(**var));
}
void
apfl_cfunc_getslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot)
{
cfunc_getslot(ctx, must_get_cfunc(ctx, cfunc), slot);
}
void
apfl_cfunc_self_getslot(apfl_ctx ctx, apfl_slotidx slot)
{
cfunc_getslot(ctx, must_get_cfunc_self(ctx), slot);
}
static void
cfunc_setslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot, apfl_stackidx value)
{
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
if (*var == NULL) {
*var = apfl_gc_new_var(&ctx->gc);
if (*var == NULL) {
apfl_raise_alloc_error(ctx);
}
**var = (struct apfl_value) { .type = VALUE_NIL };
}
**var = apfl_stack_must_pop(ctx, value);
}
void
apfl_cfunc_setslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot, apfl_stackidx value)
{
cfunc_setslot(ctx, must_get_cfunc(ctx, cfunc), slot, value);
}
void
apfl_cfunc_self_setslot(apfl_ctx ctx, apfl_slotidx slot, apfl_stackidx value)
{
cfunc_setslot(ctx, must_get_cfunc_self(ctx), slot, value);
}
void
apfl_push_userdata(apfl_ctx ctx, void *userdata)
{
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_USERDATA,
.userdata = userdata,
});
}
void *apfl_get_userdata(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_pop(ctx, index);
if (value.type != VALUE_USERDATA) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
}
return value.userdata;
}
struct apfl_format_writer apfl_get_output_writer(apfl_ctx ctx)
{
return ctx->output_writer;
}