apfl/src/eval.c

1642 lines
48 KiB
C

#include <assert.h>
#include "apfl.h"
#include "alloc.h"
#include "bytecode.h"
#include "compile.h"
#include "context.h"
#include "format.h"
#include "hashmap.h"
#include "resizable.h"
#include "strings.h"
#include "value.h"
static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse);
static void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse);
static void dispatch(apfl_ctx ctx, struct call_stack_entry *cse);
static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes);
static void
stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
{
assert(apfl_stack_drop(ctx, index));
}
#define ABSTRACT_GET_ARGUMENT(i, ilist, arg) \
if (*i >= ilist->len) { \
return false; \
} \
\
*arg = ilist->instructions[(*i)++]; \
return true;
#define ABSTRACT_MUST_GET_ARG(get, ctx, i, ilist, arg) \
if (!get(i, ilist, arg)) { \
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode); \
}
static bool
get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
{
ABSTRACT_GET_ARGUMENT(i, ilist, arg)
}
static void
must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
{
ABSTRACT_MUST_GET_ARG(get_argument, ctx, i, ilist, arg)
}
static bool
get_matcher_argument(size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg)
{
ABSTRACT_GET_ARGUMENT(i, milist, arg)
}
static void
must_get_matcher_argument(apfl_ctx ctx, size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg)
{
ABSTRACT_MUST_GET_ARG(get_matcher_argument, ctx, i, milist, arg)
}
enum scope_type {
SCOPE_LOCAL,
SCOPE_CLOSUE,
SCOPE_GLOBAL,
};
static struct scope *
get_scope(apfl_ctx ctx, struct scopes scopes, enum scope_type type)
{
switch (type) {
case SCOPE_LOCAL:
return scopes.local;
case SCOPE_CLOSUE:
return scopes.closure;
case SCOPE_GLOBAL:
return ctx->globals;
}
assert(false);
return NULL;
}
static struct scope *
get_or_create_local_scope(apfl_ctx ctx, struct scopes *scopes)
{
if (scopes->local != NULL) {
return scopes->local;
}
if ((scopes->local = apfl_scope_new(&ctx->gc)) == NULL) {
apfl_raise_alloc_error(ctx);
}
return scopes->local;
}
static bool
try_variable_get_for_scope_type(
apfl_ctx ctx,
struct scopes scopes,
struct apfl_string *name,
enum scope_type type
) {
struct apfl_value value;
struct scope *scope;
if ((scope = get_scope(ctx, scopes, type)) != NULL) {
if (apfl_scope_get(scope, name, &value)) {
apfl_stack_must_push(ctx, value);
return true;
}
}
return false;
}
static void
variable_get(apfl_ctx ctx, struct scopes scopes, struct apfl_string *name, bool global)
{
if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_LOCAL)) {
return;
}
if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_CLOSUE)) {
return;
}
if (global && try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_GLOBAL)) {
return;
}
apfl_raise_errorfmt(ctx, "Variable {string} does not exist.", apfl_string_view_from(*name));
}
static bool
try_variable_update_existing_for_scope_type(
struct apfl_string *name,
struct apfl_value value,
struct scope *scope
) {
if (scope == NULL) {
return false;
}
return apfl_scope_update_existing(scope, name, value);
}
static void
variable_set_value(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local, struct apfl_value value)
{
bool was_set = false;
if (!local) {
was_set = try_variable_update_existing_for_scope_type(name, value, scopes->local)
|| try_variable_update_existing_for_scope_type(name, value, scopes->closure);
}
if (!was_set) {
struct scope *scope = get_or_create_local_scope(ctx, scopes);
assert(scope != NULL /*get_or_create_local_scope should never return NULL*/);
if (!apfl_scope_set(&ctx->gc, scope, name, value)) {
apfl_raise_alloc_error(ctx);
}
}
}
static void
variable_set(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool keep_on_stack, bool local)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
variable_set_value(ctx, scopes, name, local, value);
if (keep_on_stack) {
// If the value should be kept on the stack, the value is now in two
// places. We need to set the COW flag to prevent mutations of one copy
// affecting the other one.
value = apfl_value_set_cow_flag(value);
} else {
stack_must_drop(ctx, -1);
}
}
static void
variable_new(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local)
{
if (!local) {
if (scopes->local != NULL && apfl_scope_has(scopes->local, name)) {
return;
}
if (scopes->closure != NULL && apfl_scope_has(scopes->closure, name)) {
return;
}
}
struct scope *scope = get_or_create_local_scope(ctx, scopes);
if (!apfl_scope_create_var(&ctx->gc, scope, name)) {
apfl_raise_alloc_error(ctx);
}
}
static void
matcher_push(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist)
{
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (!apfl_resizable_ensure_cap_for_more_elements(
ctx->gc.allocator,
sizeof(struct matcher *),
(void **)&matcher_stack->items,
matcher_stack->len,
&matcher_stack->cap,
1
)) {
apfl_raise_alloc_error(ctx);
}
if ((matcher_stack->items[matcher_stack->len] = apfl_matcher_new(&ctx->gc, milist)) == NULL) {
apfl_raise_alloc_error(ctx);
}
matcher_stack->len++;
}
static struct matcher *
matcher_stack_top(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (matcher_stack->len == 0) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
struct matcher *matcher = matcher_stack->items[matcher_stack->len-1];
if (matcher == NULL) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
return matcher;
}
static void
matcher_stack_drop(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
struct matcher_stack *matcher_stack = &cse->matcher_stack;
if (matcher_stack->len == 0) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
assert(
// We're shrinking, should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct matcher *),
(void **)&matcher_stack->items,
&matcher_stack->len,
&matcher_stack->cap,
matcher_stack->len-1
)
);
}
static void
func_inner(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count)
{
struct scope *scope = apfl_closure_scope_for_func(ctx, cse->scopes);
if (scope == NULL) {
apfl_raise_alloc_error(ctx);
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE))) {
apfl_raise_alloc_error(ctx);
}
struct apfl_value *func_value = apfl_stack_push_placeholder(ctx);
if (func_value == NULL) {
apfl_raise_alloc_error(ctx);
}
if ((func_value->func = apfl_func_new(&ctx->gc, count, scope, cse->execution_line)) == NULL) {
stack_must_drop(ctx, -1);
apfl_raise_alloc_error(ctx);
}
func_value->type = VALUE_FUNC;
}
static void
func(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
func_inner(ctx, cse, count);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
func_add_subfunc(apfl_ctx ctx, struct func_call_stack_entry *cse, struct instruction_list *body)
{
// TODO: Better error messsages
struct apfl_value value = apfl_stack_must_get(ctx, -1);
if (value.type != VALUE_FUNC) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
if (!apfl_func_add_subfunc(value.func, body, matcher_stack_top(ctx, cse))) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
matcher_stack_drop(ctx, cse);
}
static void
func_set_name(apfl_ctx ctx, struct apfl_string *name)
{
struct apfl_value value = apfl_stack_must_get(ctx, -1);
if (value.type != VALUE_FUNC) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
apfl_func_set_name(value.func, name);
}
static bool
try_call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
{
return apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct call_stack_entry),
(void**)&ctx->call_stack.items,
&ctx->call_stack.len,
&ctx->call_stack.cap,
&cse,
1
);
}
static void
call_stack_push(apfl_ctx ctx, struct call_stack_entry cse)
{
if (!try_call_stack_push(ctx, cse)) {
apfl_call_stack_entry_deinit(ctx->gc.allocator, &cse);
apfl_raise_alloc_error(ctx);
}
}
static void
call_stack_drop(apfl_ctx ctx)
{
assert(ctx->call_stack.len > 0);
apfl_call_stack_entry_deinit(ctx->gc.allocator, apfl_call_stack_cur_entry(ctx));
assert(
// We're shrinking the memory here, 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,
ctx->call_stack.len - 1
)
);
}
static void
return_from_function_inner(apfl_ctx ctx)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, -1)) {
// No return value on the stack. Return nil instead
value = (struct apfl_value) { .type = VALUE_NIL };
}
call_stack_drop(ctx);
apfl_stack_must_push(ctx, value);
}
static void
return_from_function(apfl_ctx ctx)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
return_from_function_inner(ctx);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
prepare_call(apfl_ctx ctx, size_t tmproots, struct apfl_value args, struct call_stack_entry cse)
{
call_stack_push(ctx, cse);
// Note: This pushes args on the stack of the newly created call stack
apfl_stack_must_push(ctx, args);
// Both the function and the args are now rooted again, we can undo the tmproots early.
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
// Keep evaluate instructions until we've returned from the current call stack.
// Must not be called with a APFL_CSE_CFUNCTION on top of the call stack.
static void
evaluate_until_call_stack_return(apfl_ctx ctx)
{
struct call_stack *call_stack = &ctx->call_stack;
size_t depth_started = call_stack->len;
assert(depth_started > 0);
while (call_stack->len >= depth_started) {
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
switch (cse->type) {
case APFL_CSE_CFUNCTION:
assert(false);
break;
case APFL_CSE_FUNCTION:
evaluate(ctx, &cse->func);
break;
case APFL_CSE_MATCHER:
evaluate_matcher(ctx, &cse->matcher);
break;
case APFL_CSE_FUNCTION_DISPATCH:
dispatch(ctx, cse);
break;
}
}
}
static void
must_tmproot_add_value(apfl_ctx ctx, struct apfl_value value)
{
if (!apfl_value_add_as_tmproot(&ctx->gc, value)) {
apfl_raise_alloc_error(ctx);
}
}
static void
call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackidx args_index, bool call_from_apfl)
{
struct apfl_value func = apfl_stack_must_get(ctx, func_index);
must_tmproot_add_value(ctx, func);
struct apfl_value args = apfl_stack_must_get(ctx, args_index);
must_tmproot_add_value(ctx, args);
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){func_index, args_index}));
if (!VALUE_IS_A(func, APFL_VALUE_FUNC)) {
apfl_raise_errorfmt(ctx, "Can only call functions, got a {value:type} instead", func);
}
if (!VALUE_IS_A(args, APFL_VALUE_LIST)) {
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
}
switch (func.type) {
case VALUE_FUNC: {
struct scope *local_scope = apfl_scope_new(&ctx->gc);
if (local_scope == NULL) {
apfl_raise_alloc_error(ctx);
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(local_scope, GC_TYPE_SCOPE))) {
apfl_raise_alloc_error(ctx);
}
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = APFL_CSE_FUNCTION_DISPATCH,
.stack = apfl_stack_new(),
.func_dispatch = {
.subfunc = 0,
.scopes = {
.local = local_scope,
.closure = func.func->scope,
},
.function = func.func,
.returning_from_matcher = false,
.matcher_result = false,
},
});
if (call_from_apfl) {
// In this case we're already coming from evaluate_until_call_stack_return,
// which will pick up the new stack entry. This way we can avoid doing the recursion in C.
return;
} else {
evaluate_until_call_stack_return(ctx);
}
break;
}
case VALUE_CFUNC:
prepare_call(ctx, tmproots, args, (struct call_stack_entry) {
.type = APFL_CSE_CFUNCTION,
.stack = apfl_stack_new(),
.cfunc = {
.func = func.cfunc,
},
});
func.cfunc->func(ctx);
return_from_function(ctx);
break;
default:
assert(false); // Otherwise the VALUE_IS_A() check for APFL_VALUE_FUNC would have failed
}
}
static void
call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index, bool call_from_apfl)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
call_inner(ctx, tmproots, func_index, args_index, call_from_apfl);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
void
apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index)
{
call(ctx, func_index, args_index, false);
}
static void
matcher_set_val(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t index)
{
struct matcher *matcher = matcher_stack_top(ctx, cse);
if (index >= matcher->instructions->value_count) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
matcher->values[index] = apfl_stack_must_pop(ctx, -1);
}
static void
matcher_must_match(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct matcher *matcher = matcher_stack_top(ctx, cse);
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER))) {
apfl_raise_alloc_error(ctx);
}
matcher_stack_drop(ctx, cse);
matcher_init_matching(ctx, matcher, cse->scopes);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse)
{
if (cse->returning_from_matcher) {
if (!cse->matcher_result) {
apfl_raise_const_error(ctx, apfl_messages.value_doesnt_match);
}
cse->returning_from_matcher = false;
}
union instruction_or_arg arg;
size_t *pc = &cse->pc;
struct instruction_list *ilist = cse->instructions;
while (*pc < cse->instructions->len) {
switch (ilist->instructions[(*pc)++].instruction) {
case INSN_NIL:
apfl_push_nil(ctx);
goto continue_loop;
case INSN_TRUE:
apfl_push_bool(ctx, true);
goto continue_loop;
case INSN_FALSE:
apfl_push_bool(ctx, false);
goto continue_loop;
case INSN_NUMBER:
must_get_argument(ctx, pc, ilist, &arg);
apfl_push_number(ctx, arg.number);
goto continue_loop;
case INSN_STRING:
must_get_argument(ctx, pc, ilist, &arg);
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_STRING,
.string = arg.string,
});
goto continue_loop;
case INSN_LIST:
must_get_argument(ctx, pc, ilist, &arg);
apfl_list_create(ctx, arg.count);
goto continue_loop;
case INSN_LIST_APPEND:
apfl_list_append(ctx, -2, -1);
goto continue_loop;
case INSN_LIST_EXPAND_INTO:
apfl_list_append_list(ctx, -2, -1);
goto continue_loop;
case INSN_DICT:
apfl_dict_create(ctx);
goto continue_loop;
case INSN_DICT_APPEND_KVPAIR:
apfl_dict_set(ctx, -3, -2, -1);
goto continue_loop;
case INSN_GET_MEMBER:
apfl_get_member(ctx, -2, -1);
goto continue_loop;
case INSN_VAR_NEW:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, &cse->scopes, arg.string, false);
goto continue_loop;
case INSN_VAR_NEW_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_new(ctx, &cse->scopes, arg.string, true);
goto continue_loop;
case INSN_VAR_GET:
must_get_argument(ctx, pc, ilist, &arg);
variable_get(ctx, cse->scopes, arg.string, true);
goto continue_loop;
case INSN_VAR_SET:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, &cse->scopes, arg.string, true, false);
goto continue_loop;
case INSN_VAR_SET_LOCAL:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, &cse->scopes, arg.string, true, true);
goto continue_loop;
case INSN_MOVE_TO_LOCAL_VAR:
must_get_argument(ctx, pc, ilist, &arg);
variable_set(ctx, &cse->scopes, arg.string, false, true);
goto continue_loop;
case INSN_NEXT_LINE:
cse->execution_line++;
goto continue_loop;
case INSN_SET_LINE:
must_get_argument(ctx, pc, ilist, &arg);
cse->execution_line = arg.count;
goto continue_loop;
case INSN_GET_BY_INDEX_KEEP:
must_get_argument(ctx, pc, ilist, &arg);
apfl_get_list_member_by_index(ctx, -1, arg.index);
goto continue_loop;
case INSN_DROP:
if (!apfl_stack_drop(ctx, -1)) {
apfl_raise_invalid_stackidx(ctx);
}
goto continue_loop;
case INSN_DUP:
apfl_copy(ctx, -1);
goto continue_loop;
case INSN_CALL:
call(ctx, -2, -1, true);
// By returning from this function, the newly pushed call stack entry (if any) will get picked up by
// evaluate_until_call_stack_return. In case no new CSE was pushed (when a cfunc was called), we'll the
// simply continue with the current call stack.
return;
case INSN_FUNC:
must_get_argument(ctx, pc, ilist, &arg);
func(ctx, cse, arg.count);
goto continue_loop;
case INSN_FUNC_ADD_SUBFUNC:
must_get_argument(ctx, pc, ilist, &arg);
func_add_subfunc(ctx, cse, arg.body);
goto continue_loop;
case INSN_FUNC_SET_NAME:
must_get_argument(ctx, pc, ilist, &arg);
func_set_name(ctx, arg.string);
goto continue_loop;
case INSN_MATCHER_PUSH:
must_get_argument(ctx, pc, ilist, &arg);
matcher_push(ctx, cse, arg.matcher);
goto continue_loop;
case INSN_MATCHER_SET_VAL:
must_get_argument(ctx, pc, ilist, &arg);
matcher_set_val(ctx, cse, arg.index);
goto continue_loop;
case INSN_MATCHER_MUST_MATCH:
// matcher_must_match pushes a new call stack entry for the matcher onto the stack. We return from this
// So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher,
// once it returns.
matcher_must_match(ctx, cse);
return;
}
assert(false);
continue_loop:;
}
return_from_function(ctx);
}
static void
matcher_state_push(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct matcher_state entry)
{
if (!apfl_resizable_append(
ctx->gc.allocator,
sizeof(struct matcher_state),
(void **)&cse->matcher_state_stack,
&cse->matcher_state_stack_len,
&cse->matcher_state_stack_cap,
&entry,
1
)) {
apfl_raise_alloc_error(ctx);
}
}
APFL_NORETURN static void
raise_invalid_matcher_state(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, apfl_messages.invalid_matcher_state);
}
static void
matcher_state_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
if (cse->matcher_state_stack_len == 0) {
raise_invalid_matcher_state(ctx);
}
assert(
// We're shrinking, should not fail
apfl_resizable_resize(
ctx->gc.allocator,
sizeof(struct matcher_state),
(void **)&cse->matcher_state_stack,
&cse->matcher_state_stack_len,
&cse->matcher_state_stack_cap,
cse->matcher_state_stack_len-1
)
);
}
static void
matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes)
{
struct apfl_value value = apfl_stack_must_pop(ctx, -1);
must_tmproot_add_value(ctx, value);
if (matcher == NULL) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
size_t capture_count = matcher->instructions->capture_count;
struct matcher_call_stack_entry matcher_cse = {
.pc = 0,
.from_predicate = false,
.matcher = matcher,
.scopes = scopes,
.capture_index = 0,
.capture_count = capture_count,
.captures = NULL,
.transfers = NULL,
.matcher_state_stack = NULL,
.matcher_state_stack_len = 0,
.matcher_state_stack_cap = 0,
};
if (capture_count > 0) {
if ((matcher_cse.captures = ALLOC_LIST(ctx->gc.allocator, struct apfl_value, capture_count)) == NULL) {
goto error;
}
for (size_t i = 0; i < capture_count; i++) {
matcher_cse.captures[i] = (struct apfl_value) { .type = VALUE_NIL };
}
if ((matcher_cse.transfers = ALLOC_LIST(
ctx->gc.allocator,
struct matcher_capture_transfer,
capture_count
)) == NULL) {
goto error;
}
for (size_t i = 0; i < capture_count; i++) {
matcher_cse.transfers[i] = (struct matcher_capture_transfer) {
.var = NULL,
.path_start = 0,
.path_len = 0,
.local = false,
};
}
}
if ((matcher_cse.matcher_state_stack = ALLOC_LIST(
ctx->gc.allocator,
struct matcher_state,
1
)) == NULL) {
goto error;
}
matcher_cse.matcher_state_stack[0] = (struct matcher_state) {
.mode = MATCHER_MODE_VALUE,
};
matcher_cse.matcher_state_stack_len = 1;
matcher_cse.matcher_state_stack_cap = 1;
if (!try_call_stack_push(ctx, (struct call_stack_entry) {
.type = APFL_CSE_MATCHER,
.stack = apfl_stack_new(),
.matcher = matcher_cse,
})) {
goto error;
}
// No need for `goto error` on failure here, all dynamically allocated
// elements are on the call stack now, so the GC can clean them up in case
// of an error.
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value));
return;
error:
apfl_matcher_call_stack_entry_deinit(ctx->gc.allocator, &matcher_cse);
apfl_raise_alloc_error(ctx);
}
/*
* Initialise matching. Pushes a new call stack and pops a value of the current+
* value stack.
*/
static void
matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
matcher_init_matching_inner(ctx, matcher, scopes);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
static void
matcher_check_index(apfl_ctx ctx, size_t count, size_t index)
{
if (index >= count) {
apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode);
}
}
static struct matcher_state *
matcher_cur_state(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
if (cse->matcher_state_stack_len == 0) {
raise_invalid_matcher_state(ctx);
}
return &cse->matcher_state_stack[cse->matcher_state_stack_len-1];
}
static bool
matcher_current_val_in_state(apfl_ctx ctx, struct matcher_state *state, struct apfl_value *value)
{
struct apfl_value cur;
switch (state->mode) {
case MATCHER_MODE_VALUE:
case MATCHER_MODE_LIST_REMAINING:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
*value = cur;
return true;
case MATCHER_MODE_STOP:
case MATCHER_MODE_LIST_UNDERFLOW:
return false;
case MATCHER_MODE_LIST_START:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (cur.type != VALUE_LIST) {
raise_invalid_matcher_state(ctx);
}
if (state->lower >= cur.list->len) {
return false;
}
*value = cur.list->items[state->lower];
return true;
case MATCHER_MODE_LIST_END:
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (cur.type != VALUE_LIST) {
raise_invalid_matcher_state(ctx);
}
if (state->upper == 0) {
return NULL;
}
*value = cur.list->items[state->upper-1];
return true;
}
raise_invalid_matcher_state(ctx);
}
static bool
matcher_current_val(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct apfl_value *value)
{
struct matcher_state *state = matcher_cur_state(ctx, cse);
return matcher_current_val_in_state(ctx, state, value);
}
static bool
matcher_next(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
again:;
struct matcher_state *state = matcher_cur_state(ctx, cse);
switch (state->mode) {
case MATCHER_MODE_VALUE:
state->mode = MATCHER_MODE_STOP;
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
return true;
case MATCHER_MODE_STOP:
case MATCHER_MODE_LIST_UNDERFLOW:
raise_invalid_matcher_state(ctx);
return false;
case MATCHER_MODE_LIST_START:
state->lower++;
return true;
case MATCHER_MODE_LIST_END:
if (state->upper <= state->lower) {
state->mode = MATCHER_MODE_LIST_UNDERFLOW;
return false;
}
state->upper--;
return true;
case MATCHER_MODE_LIST_REMAINING:
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
matcher_state_drop(ctx, cse);
goto again; // We also need to advance the previous stack entry,
// like we would do when doing a MATCHER_LEAVE_LIST
}
raise_invalid_matcher_state(ctx);
}
static bool
matcher_enter_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_state *state = matcher_cur_state(ctx, cse);
struct apfl_value cur;
if (!matcher_current_val_in_state(ctx, state, &cur)) {
return false;
}
if (cur.type != VALUE_LIST) {
return false;
}
size_t len = cur.list->len;
apfl_stack_must_push(ctx, cur);
matcher_state_push(ctx, cse, (struct matcher_state) {
.mode = MATCHER_MODE_LIST_START,
.lower = 0,
.upper = len,
});
return true;
}
static void
matcher_continue_from_end(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_state *state = matcher_cur_state(ctx, cse);
if (state->mode != MATCHER_MODE_LIST_START) {
raise_invalid_matcher_state(ctx);
}
state->mode = MATCHER_MODE_LIST_END;
}
static void
matcher_remainding(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_state *state = matcher_cur_state(ctx, cse);
struct apfl_value cur;
if (!apfl_stack_get(ctx, &cur, -1)) {
raise_invalid_matcher_state(ctx);
}
if (
(state->mode != MATCHER_MODE_LIST_START && state->mode != MATCHER_MODE_LIST_END)
|| cur.type != VALUE_LIST
) {
raise_invalid_matcher_state(ctx);
}
if (state->lower > state->upper) {
raise_invalid_matcher_state(ctx);
}
struct list_header *cur_list = cur.list;
assert(cur_list->len >= state->upper);
size_t len = state->upper - state->lower;
apfl_list_create(ctx, len);
struct apfl_value new_val = apfl_stack_must_get(ctx, -1);
assert(new_val.type == VALUE_LIST);
struct list_header *new_list = new_val.list;
assert(new_list->cap == len);
assert(new_list->len == 0);
for (size_t i = state->lower; i < state->upper; i++) {
new_list->items[new_list->len++] = cur_list->items[i];
}
assert(new_list->len == len);
if (!apfl_stack_drop(ctx, -2)) { // Drop the original list
raise_invalid_matcher_state(ctx);
}
state->mode = MATCHER_MODE_LIST_REMAINING;
}
static bool
matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
struct matcher_state *state = matcher_cur_state(ctx, cse);
if (state->mode != MATCHER_MODE_LIST_START) {
raise_invalid_matcher_state(ctx);
}
if (state->lower < state->upper) {
// List was not completely matched
return false;
}
if (!apfl_stack_drop(ctx, -1)) {
raise_invalid_matcher_state(ctx);
}
matcher_state_drop(ctx, cse);
return matcher_next(ctx, cse);
}
static void
matcher_transfer(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
for (size_t i = 0; i < cse->capture_count; i++) {
struct matcher_capture_transfer transfer = cse->transfers[i];
if (transfer.path_len == 0) {
variable_set_value(ctx, &cse->scopes, transfer.var, transfer.local, cse->captures[i]);
} else {
// Set the value at a key path in a (nested) dictionary.
variable_get(ctx, cse->scopes, transfer.var, false);
if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) {
apfl_raise_errorfmt(ctx, "Can not update value of type {stack:type}, expected dict", -1);
}
// Get or create intermediary dictionaries along the key path and leave a copy of the previous one on the
// stack, so we can set the result in reverse order there later.
for (size_t i = 0; i < transfer.path_len - 1; i++) {
apfl_copy(ctx, -1);
size_t value_index = transfer.path_start + i;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
if (apfl_get_member_if_exists(ctx, -2, -1)) {
if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) {
apfl_raise_errorfmt(ctx, "Can not update value of type {stack:type}, expected dict", -1);
}
} else {
apfl_dict_create(ctx);
}
}
// Set the value to the rightmost dictionary key
size_t value_index = transfer.path_start + transfer.path_len - 1;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
apfl_stack_must_push(ctx, cse->captures[i]);
apfl_dict_set(ctx, -3, -2, -1);
// Go through the key path (minus the rightmost key) in reverse order and set the value in the intermediary
// dictionaries. Note that i has a offset of one here so we can use the i > 0 check in the loop (>= 0 would
// not work as size_t is unsigned).
for (size_t i = transfer.path_len - 1; i > 0; i--) {
size_t value_index = transfer.path_start + i - 1;
matcher_check_index(ctx, cse->matcher->value_count, value_index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index]));
apfl_dict_set(ctx, -3, -1, -2);
}
// Finally set the copied and modified dictionary to the variable again.
variable_set(ctx, &cse->scopes, transfer.var, false, false);
}
}
}
static void
return_from_matcher(apfl_ctx ctx, bool result)
{
struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
assert(cse->type == APFL_CSE_MATCHER);
if (result) {
matcher_transfer(ctx, &cse->matcher);
}
call_stack_drop(ctx);
cse = apfl_call_stack_cur_entry(ctx);
assert(cse != NULL);
switch (cse->type) {
case APFL_CSE_FUNCTION:
cse->func.returning_from_matcher = true;
cse->func.matcher_result = result;
break;
case APFL_CSE_FUNCTION_DISPATCH:
cse->func_dispatch.returning_from_matcher = true;
cse->func_dispatch.matcher_result = result;
break;
default:
assert(false /* Invalid stack entry below matcher stack */);
}
}
#define RETURN_WITHOUT_MATCH(ctx) \
do { \
return_from_matcher((ctx), false); \
return; \
} while (0)
#define RETURN_WITHOUT_MATCH_ON_FALSE(ctx, x) \
do { \
if (!(x)) { \
RETURN_WITHOUT_MATCH(ctx); \
} \
} while (0)
static bool
matcher_evaluate_capturing_instruction(
apfl_ctx ctx,
struct matcher_call_stack_entry *cse,
bool local,
bool with_path
) {
size_t *pc = &cse->pc;
struct matcher *matcher = cse->matcher;
struct matcher_instruction_list *milist = matcher->instructions;
struct matcher_capture_transfer transfer = {
.var = NULL,
.path_start = 0,
.path_len = 0,
.local = local,
};
union matcher_instruction_or_arg arg;
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.var = arg.string;
if (with_path) {
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.path_start = arg.index;
must_get_matcher_argument(ctx, pc, milist, &arg);
transfer.path_len = arg.index;
}
struct apfl_value cur;
if (!matcher_current_val(ctx, cse, &cur)) {
return false;
}
size_t capture = cse->capture_index++;
matcher_check_index(ctx, milist->capture_count, capture);
cse->captures[capture] = apfl_value_set_cow_flag(cur);
cse->transfers[capture] = transfer;
return matcher_next(ctx, cse);
}
static void
evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse)
{
union matcher_instruction_or_arg arg;
if (cse->from_predicate) {
cse->from_predicate = false;
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, apfl_is_truthy(ctx, -1));
}
size_t *pc = &cse->pc;
struct matcher *matcher = cse->matcher;
struct matcher_instruction_list *milist = matcher->instructions;
while (*pc < milist->len) {
struct apfl_value cur;
switch (milist->instructions[(*pc)++].instruction) {
case MATCHER_CAPTURE_TO_VAR:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, false, false)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_LOCAL:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, true, false)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_WITH_PATH:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, false, true)
);
goto continue_loop;
case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH:
RETURN_WITHOUT_MATCH_ON_FALSE(
ctx,
matcher_evaluate_capturing_instruction(ctx, cse, true, true)
);
goto continue_loop;
case MATCHER_IGNORE:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse));
goto continue_loop;
case MATCHER_CHECK_CONST:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->value_count, arg.index);
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, apfl_value_eq(matcher->values[arg.index], cur));
goto continue_loop;
case MATCHER_CHECK_PRED:
if (!matcher_current_val(ctx, cse, &cur)) {
RETURN_WITHOUT_MATCH(ctx);
}
must_get_matcher_argument(ctx, pc, milist, &arg);
matcher_check_index(ctx, milist->value_count, arg.index);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(matcher->values[arg.index]));
apfl_list_create(ctx, 1);
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cur));
apfl_list_append(ctx, -2, -1);
cse->from_predicate = true;
call(ctx, -2, -1, true);
// By returning from this function, the newly pushed call stack entry (if any) will get picked up by
// evaluate_until_call_stack_return. In case no new CSE was pushed (when a cfunc was called), we'll the
// simply continue with the current call stack.
return;
case MATCHER_ENTER_LIST:
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_enter_list(ctx, cse));
goto continue_loop;
case MATCHER_LEAVE_LIST:
RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_leave_list(ctx, cse));
goto continue_loop;
case MATCHER_CONTINUE_FROM_END:
matcher_continue_from_end(ctx, cse);
goto continue_loop;
case MATCHER_REMAINDING:
matcher_remainding(ctx, cse);
goto continue_loop;
}
assert(false);
continue_loop:;
}
return_from_matcher(
ctx,
// We've successfully matched everything, if there's only one stack element left and we're in stop state
cse->matcher_state_stack_len == 1 && cse->matcher_state_stack[0].mode == MATCHER_MODE_STOP
);
}
struct matcher_stack
matcher_stack_new(void)
{
return (struct matcher_stack) {
.items = NULL,
.len = 0,
.cap = 0,
};
}
static void
dispatch(apfl_ctx ctx, struct call_stack_entry *cse)
{
assert(cse->type == APFL_CSE_FUNCTION_DISPATCH);
struct func_dispatch_call_stack_entry *fd_cse = &cse->func_dispatch;
struct function *function = fd_cse->function;
if (fd_cse->returning_from_matcher) {
if (fd_cse->matcher_result) {
struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc];
// Replace the current CSE with a function CSE
cse->type = APFL_CSE_FUNCTION;
cse->stack.len = 0;
cse->func = (struct func_call_stack_entry) {
.pc = 0,
.instructions = subfunction->body,
.scopes = fd_cse->scopes,
.execution_line = subfunction->body->line,
.matcher_stack = matcher_stack_new(),
.returning_from_matcher = false,
.matcher_result = false,
.function = function,
.subfunction_index = fd_cse->subfunc,
};
return;
}
fd_cse->subfunc++;
fd_cse->returning_from_matcher = false;
}
if (fd_cse->subfunc >= function->subfunctions_len) {
apfl_raise_const_error(ctx, apfl_messages.no_matching_subfunction);
}
// matcher_init_matching consumes the value on the top of the stack, we need
// to copy the value for further subfunctions.
apfl_copy(ctx, -1);
matcher_init_matching(
ctx,
function->subfunctions[fd_cse->subfunc].matcher,
fd_cse->scopes
);
}
enum interative_runner_state {
IRUNNER_OK,
IRUNNER_EOF,
IRUNNER_ERR,
};
struct apfl_iterative_runner_data {
apfl_ctx ctx;
apfl_tokenizer_ptr tokenizer;
apfl_parser_ptr parser;
enum apfl_result result;
enum interative_runner_state state;
struct scope *scope;
};
static void
iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr expr)
{
apfl_ctx ctx = runner->ctx;
struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line);
if (ilist == NULL) {
apfl_raise_alloc_error(ctx);
}
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) {
apfl_raise_alloc_error(ctx);
}
struct apfl_error error;
if (!apfl_compile(&ctx->gc, expr, &error, ilist)) {
apfl_raise_error_object(ctx, error);
}
call_stack_push(ctx, (struct call_stack_entry) {
.type = APFL_CSE_FUNCTION,
.stack = apfl_stack_new(),
.func = (struct func_call_stack_entry) {
.pc = 0,
.instructions = ilist,
.scopes = {
.local = runner->scope,
.closure = NULL,
},
.execution_line = ilist->line,
.matcher_stack = matcher_stack_new(),
.returning_from_matcher = false,
},
});
evaluate_until_call_stack_return(ctx);
}
static void
iterative_runner_eval_expr(apfl_iterative_runner runner, struct apfl_expr expr)
{
apfl_ctx ctx = runner->ctx;
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
iterative_runner_eval_expr_inner(runner, expr);
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
}
bool
apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, struct apfl_format_writer w)
{
struct apfl_value value;
if (!apfl_stack_pop(ctx, &value, index)) {
FMT_TRY(apfl_format_put_string(w, "apfl_debug_print_val: Invalid stack index "));
FMT_TRY(apfl_format_put_int(w, (int)index));
FMT_TRY(apfl_format_put_string(w, "\n"));
return true;
}
return apfl_value_print(value, w);
}
apfl_iterative_runner
apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader)
{
apfl_iterative_runner runner = NULL;
apfl_tokenizer_ptr tokenizer = NULL;
apfl_parser_ptr parser = NULL;
runner = ALLOC_OBJ(ctx->gc.allocator, struct apfl_iterative_runner_data);
if (runner == NULL) {
return NULL;
}
tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader);
if (tokenizer == NULL) {
goto error;
}
parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer));
if (parser == NULL) {
goto error;
}
struct scope *scope = apfl_scope_new(&ctx->gc);
if (scope == NULL) {
goto error;
}
*runner = (struct apfl_iterative_runner_data) {
.ctx = ctx,
.tokenizer = tokenizer,
.parser = parser,
.result = APFL_RESULT_OK,
.state = IRUNNER_OK,
.scope = scope,
};
if (!apfl_ctx_register_iterative_runner(ctx, runner)) {
goto error;
}
return runner;
error:
FREE_OBJ(ctx->gc.allocator, runner);
apfl_tokenizer_destroy(tokenizer);
apfl_parser_destroy(parser);
return NULL;
}
static void
iterative_runner_next_protected(apfl_ctx ctx, void *opaque)
{
(void)ctx;
apfl_iterative_runner runner = opaque;
switch (apfl_parser_next(runner->parser)) {
case APFL_PARSE_OK:
iterative_runner_eval_expr(runner, apfl_parser_get_expr(runner->parser));
return;
case APFL_PARSE_ERROR: {
struct apfl_error err = apfl_parser_get_error(runner->parser);
if (err.type == APFL_ERR_INPUT_ERROR) {
runner->state = IRUNNER_ERR;
}
apfl_raise_error_object(runner->ctx, err);
return;
}
case APFL_PARSE_EOF:
runner->state = IRUNNER_EOF;
return;
}
assert(false);
}
#define DECORATE_TRY_FMT(ctx, x) do { if (!(x)) { apfl_raise_alloc_error(ctx); } } while (0)
static void
iterative_runner_decorate_error(apfl_ctx ctx, void *opaque)
{
(void)opaque;
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
struct apfl_format_writer w = apfl_format_string_writer(&sb);
apfl_tostring(ctx, -1);
if (!(
apfl_format_put_string(w, apfl_get_string(ctx, -1))
&& apfl_format_put_string(w, "\n\nBacktrace:")
)) {
goto fail;
}
size_t depth = apfl_call_stack_depth(ctx);
for (size_t i = 0; i < depth; i++) {
if (!(
apfl_format_put_string(w, "\n")
&& apfl_format_put_string(w, "#")
&& apfl_format_put_int(w, (int)i+1)
&& apfl_format_put_string(w, ": ")
&& apfl_call_stack_entry_info_format(
w,
apfl_call_stack_inspect(ctx, i)
)
)) {
goto fail;
}
}
struct apfl_string string = apfl_string_builder_move_string(&sb);
apfl_string_builder_deinit(&sb);
if (!apfl_move_string_onto_stack(ctx, string)) {
apfl_raise_alloc_error(ctx);
}
return;
fail:
apfl_string_builder_deinit(&sb);
apfl_raise_alloc_error(ctx);
}
bool
apfl_iterative_runner_next(apfl_iterative_runner runner)
{
if (runner->state != IRUNNER_OK) {
return false;
}
apfl_stack_clear(runner->ctx);
runner->result = apfl_do_protected(
runner->ctx,
iterative_runner_next_protected,
runner,
iterative_runner_decorate_error
);
return runner->state == IRUNNER_OK;
}
enum apfl_result
apfl_iterative_runner_get_result(apfl_iterative_runner runner)
{
return runner->result;
}
bool
apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner runner)
{
return runner->state == IRUNNER_ERR;
}
bool
apfl_iterative_runner_run_repl(
apfl_iterative_runner runner,
struct apfl_format_writer w_out,
struct apfl_format_writer w_err
) {
apfl_ctx ctx = runner->ctx;
while (apfl_iterative_runner_next(runner)) {
switch (apfl_iterative_runner_get_result(runner)) {
case APFL_RESULT_OK :
if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) {
apfl_drop(ctx, -1);
} else {
FMT_TRY(apfl_debug_print_val(ctx, -1, w_out));
}
break;
case APFL_RESULT_ERR:
FMT_TRY(apfl_format_put_string(w_err, "Error occurred during evaluation:\n"));
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
FMT_TRY(apfl_format_put_string(w_err, apfl_get_string(ctx, -1)));
} else {
FMT_TRY(apfl_debug_print_val(ctx, -1, w_err));
}
FMT_TRY(apfl_format_put_char(w_err, '\n'));
break;
case APFL_RESULT_ERRERR:
FMT_TRY(apfl_format_put_string(w_err, "Error occurred during error handling.\n"));
break;
case APFL_RESULT_ERR_ALLOC:
FMT_TRY(apfl_format_put_string(w_err, "Fatal: Could not allocate memory.\n"));
return false;
}
}
if (apfl_iterative_runner_stopped_because_of_error(runner)) {
return false;
}
return true;
}
void
apfl_iterative_runner_destroy(apfl_iterative_runner runner)
{
if (runner == NULL) {
return;
}
apfl_parser_destroy(runner->parser);
apfl_tokenizer_destroy(runner->tokenizer);
apfl_ctx_unregister_iterative_runner(runner->ctx, runner);
FREE_OBJ(runner->ctx->gc.allocator, runner);
}
void
apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner runner, gc_visitor visitor, void *opaque)
{
// TODO: It's a bit awkward that this function is defined here but the
// prototype lives in context.h... Maybe we should just merge context
// and eval together? The separation is rather arbitrary anyway :/
if (runner->scope != NULL) {
visitor(opaque, GC_OBJECT_FROM(runner->scope, GC_TYPE_SCOPE));
}
}