#include #include "apfl.h" #include "alloc.h" #include "bytecode.h" #include "compile.h" #include "context.h" #include "format.h" #include "hashmap.h" #include "matcher.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 matcher_init_matching(apfl_ctx ctx, struct matcher *matcher); 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_RESULT_ERR, 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) } static struct func_call_stack_entry * get_current_func_cse(apfl_ctx ctx) { struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); if (cse == NULL) { return NULL; } return cse->type == CSE_FUNCTION ? &cse->func : NULL; } enum scope_type { SCOPE_LOCAL, SCOPE_CLOSUE, SCOPE_GLOBAL, }; static struct scope * get_scope(apfl_ctx ctx, enum scope_type type) { struct func_call_stack_entry *func_cse; switch (type) { case SCOPE_LOCAL: if ((func_cse = get_current_func_cse(ctx)) != NULL) { return func_cse->scope; } return NULL; case SCOPE_CLOSUE: if ((func_cse = get_current_func_cse(ctx)) != NULL) { return func_cse->closure_scope; } return NULL; case SCOPE_GLOBAL: return ctx->globals; } assert(false); return NULL; } static struct scope * get_or_create_local_scope(apfl_ctx ctx) { struct func_call_stack_entry *func_cse = get_current_func_cse(ctx); assert(func_cse != NULL); if (func_cse->scope != NULL) { return func_cse->scope; } if ((func_cse->scope = apfl_scope_new(&ctx->gc)) == NULL) { apfl_raise_alloc_error(ctx); } return func_cse->scope; } static bool try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type) { struct apfl_value value; struct scope *scope; if ((scope = get_scope(ctx, 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 apfl_string *name) { if (try_variable_get_for_scope_type(ctx, name, SCOPE_LOCAL)) { return; } if (try_variable_get_for_scope_type(ctx, name, SCOPE_CLOSUE)) { return; } if (try_variable_get_for_scope_type(ctx, name, SCOPE_GLOBAL)) { return; } apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.variable_doesnt_exist); } static bool try_variable_update_existing_for_scope_type( apfl_ctx ctx, struct apfl_string *name, struct apfl_value value, enum scope_type type ) { struct scope *scope; if ((scope = get_scope(ctx, type)) != NULL) { if (apfl_scope_update_existing(scope, name, value)) { return true; } } return false; } static void variable_set_value(apfl_ctx ctx, 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(ctx, name, value, SCOPE_LOCAL) || try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_CLOSUE); } if (!was_set) { struct scope *scope = get_or_create_local_scope(ctx); 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 apfl_string *name, bool keep_on_stack, bool local) { struct apfl_value value = apfl_stack_must_get(ctx, -1); variable_set_value(ctx, 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 bool variable_exists_by_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type) { struct scope *scope; if ((scope = get_scope(ctx, type)) != NULL) { if (apfl_scope_has(scope, name)) { return true; } } return false; } static void variable_new(apfl_ctx ctx, struct apfl_string *name, bool local) { if (!local) { if (variable_exists_by_scope_type(ctx, name, SCOPE_LOCAL)) { return; } if (variable_exists_by_scope_type(ctx, name, SCOPE_CLOSUE)) { return; } } struct scope *scope = get_or_create_local_scope(ctx); if (!apfl_scope_create_var(&ctx->gc, scope, name)) { apfl_raise_alloc_error(ctx); } } static void func_inner(apfl_ctx ctx, struct instruction_list *body) { struct scope *scope = apfl_closure_scope_for_func(ctx); 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, body, scope)) == NULL) { stack_must_drop(ctx, -1); apfl_raise_alloc_error(ctx); } func_value->type = VALUE_FUNC; } static void func(apfl_ctx ctx, struct instruction_list *body) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); func_inner(ctx, body); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } static void call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) { if (!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 )) { 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 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 CSE_CFUNCTION: assert(false); break; case CSE_FUNCTION: evaluate(ctx, &cse->func); break; case CSE_MATCHER: evaluate_matcher(ctx, &cse->matcher); break; } } } static void must_tmproot_add_value(apfl_ctx ctx, struct apfl_value value) { struct gc_object *obj = apfl_value_get_gc_object(value); if (obj != NULL) { if (!apfl_gc_tmproot_add(&ctx->gc, obj)) { 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_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_function); } if (!VALUE_IS_A(args, APFL_VALUE_LIST)) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list); } switch (func.type) { case VALUE_FUNC: prepare_call(ctx, tmproots, args, (struct call_stack_entry) { .type = CSE_FUNCTION, .stack = apfl_stack_new(), .func = { .pc = 0, .instructions = func.func->body, .scope = NULL, .closure_scope = func.func->scope, .execution_line = func.func->body->line, .returning_from_matcher = false, .matcher = NULL, }, }); 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 = 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_load(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist) { assert(cse != NULL); if ((cse->matcher = apfl_matcher_new(&ctx->gc, milist)) == NULL) { apfl_raise_alloc_error(ctx); } } static void matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index) { if (matcher == NULL) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } if (index >= matcher->instructions->value_count) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } matcher->values[index] = apfl_stack_must_pop(ctx, -1); } static void variable_set_from_matcher_inner( apfl_ctx ctx, struct matcher *matcher, struct apfl_string *varname, size_t index, bool local ) { if (matcher == NULL) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } if (index >= matcher->instructions->capture_count) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } struct apfl_value value = apfl_value_move(&matcher->captures[index]); must_tmproot_add_value(ctx, value); variable_set_value(ctx, varname, local, value); } static void variable_set_from_matcher( apfl_ctx ctx, struct matcher *matcher, struct apfl_string *varname, size_t index, bool local ) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); variable_set_from_matcher_inner(ctx, matcher, varname, index, local); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) { if (cse->returning_from_matcher) { assert(cse->matcher != NULL); if (!cse->matcher->result) { cse->matcher = NULL; apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_doesnt_match); } cse->returning_from_matcher = false; } union instruction_or_arg arg; union instruction_or_arg arg2; 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, arg.string, false); goto continue_loop; case INSN_VAR_NEW_LOCAL: must_get_argument(ctx, pc, ilist, &arg); variable_new(ctx, arg.string, true); goto continue_loop; case INSN_VAR_GET: must_get_argument(ctx, pc, ilist, &arg); variable_get(ctx, arg.string); goto continue_loop; case INSN_VAR_SET: must_get_argument(ctx, pc, ilist, &arg); variable_set(ctx, arg.string, true, false); goto continue_loop; case INSN_VAR_SET_LOCAL: must_get_argument(ctx, pc, ilist, &arg); variable_set(ctx, arg.string, true, true); goto continue_loop; case INSN_MOVE_TO_LOCAL_VAR: must_get_argument(ctx, pc, ilist, &arg); variable_set(ctx, 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_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, arg.body); goto continue_loop; case INSN_MATCHER_LOAD: must_get_argument(ctx, pc, ilist, &arg); matcher_load(ctx, cse, arg.matcher); goto continue_loop; case INSN_MATCHER_SET_VAL: must_get_argument(ctx, pc, ilist, &arg); matcher_set_val(ctx, cse->matcher, arg.index); goto continue_loop; case INSN_MATCHER_MUST_MATCH: // matcher_init_matching pushes a new call stack entry for the matcher onto the stack. We rturn from this // So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher, // once it returns. cse->returning_from_matcher = true; matcher_init_matching(ctx, cse->matcher); return; case INSN_MATCHER_DROP: cse->matcher = NULL; goto continue_loop; case INSN_VAR_SET_FROM_MATCHER: must_get_argument(ctx, pc, ilist, &arg); must_get_argument(ctx, pc, ilist, &arg2); variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, false); goto continue_loop; case INSN_VAR_SET_LOCAL_FROM_MATCHER: must_get_argument(ctx, pc, ilist, &arg); must_get_argument(ctx, pc, ilist, &arg2); variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, true); goto continue_loop; } assert(false); continue_loop:; } return_from_function(ctx); } static void matcher_stack_push(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct matcher_stack_entry entry) { if (!apfl_resizable_append( ctx->gc.allocator, sizeof(struct matcher_stack_entry), (void **)&cse->matcher_stack, &cse->matcher_stack_len, &cse->matcher_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_RESULT_ERR, apfl_messages.invalid_matcher_state); } static void matcher_stack_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { if (cse->matcher_stack_len == 0) { raise_invalid_matcher_state(ctx); } assert( // We're shrinking, should not fail apfl_resizable_resize( ctx->gc.allocator, sizeof(struct matcher_stack_entry), (void **)&cse->matcher_stack, &cse->matcher_stack_len, &cse->matcher_stack_cap, cse->matcher_stack_len-1 ) ); } static void matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher) { struct apfl_value value = apfl_stack_must_get(ctx, -1); must_tmproot_add_value(ctx, value); if (matcher == NULL) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } struct matcher_call_stack_entry matcher_cse = { .pc = 0, .matcher = matcher, .matcher_stack = NULL, .matcher_stack_len = 0, .matcher_stack_cap = 0, }; matcher_stack_push(ctx, &matcher_cse, (struct matcher_stack_entry) { .mode = MATCHER_MODE_VALUE, }); call_stack_push(ctx, (struct call_stack_entry) { .type = CSE_MATCHER, .stack = apfl_stack_new(), .matcher = matcher_cse, }); apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value)); } static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); matcher_init_matching_inner(ctx, matcher); 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_RESULT_ERR, apfl_messages.corrupted_bytecode); } } static struct matcher_stack_entry * matcher_cur_stack_entry(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { if (cse->matcher_stack_len == 0) { raise_invalid_matcher_state(ctx); } return &cse->matcher_stack[cse->matcher_stack_len-1]; } static bool matcher_current_val_in_mstack(apfl_ctx ctx, struct matcher_stack_entry *mstack, struct apfl_value *value) { struct apfl_value cur; switch (mstack->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 (mstack->lower >= cur.list->len) { return false; } *value = cur.list->items[mstack->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 (mstack->upper == 0) { return NULL; } *value = cur.list->items[mstack->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_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); return matcher_current_val_in_mstack(ctx, mstack, value); } static bool matcher_next(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { again:; struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); switch (mstack->mode) { case MATCHER_MODE_VALUE: mstack->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: mstack->lower++; return true; case MATCHER_MODE_LIST_END: if (mstack->upper <= mstack->lower) { mstack->mode = MATCHER_MODE_LIST_UNDERFLOW; return false; } mstack->upper--; return true; case MATCHER_MODE_LIST_REMAINING: if (!apfl_stack_drop(ctx, -1)) { raise_invalid_matcher_state(ctx); } matcher_stack_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_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); struct apfl_value cur; if (!matcher_current_val_in_mstack(ctx, mstack, &cur)) { return false; } if (cur.type != VALUE_LIST) { return false; } size_t len = cur.list->len; apfl_stack_must_push(ctx, cur); matcher_stack_push(ctx, cse, (struct matcher_stack_entry) { .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_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); if (mstack->mode != MATCHER_MODE_LIST_START) { raise_invalid_matcher_state(ctx); } mstack->mode = MATCHER_MODE_LIST_END; } static void matcher_remainding(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); struct apfl_value cur; if (!apfl_stack_get(ctx, &cur, -1)) { raise_invalid_matcher_state(ctx); } if ( (mstack->mode != MATCHER_MODE_LIST_START && mstack->mode != MATCHER_MODE_LIST_END) || cur.type != VALUE_LIST ) { raise_invalid_matcher_state(ctx); } if (mstack->lower > mstack->upper) { raise_invalid_matcher_state(ctx); } struct list_header *cur_list = cur.list; assert(cur_list->len >= mstack->upper); size_t len = mstack->upper - mstack->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 = mstack->lower; i < mstack->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); } mstack->mode = MATCHER_MODE_LIST_REMAINING; } static bool matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); if (mstack->mode != MATCHER_MODE_LIST_START) { raise_invalid_matcher_state(ctx); } if (mstack->lower < mstack->upper) { // List was not completely matched return false; } if (!apfl_stack_drop(ctx, -1)) { raise_invalid_matcher_state(ctx); } matcher_stack_drop(ctx, cse); return matcher_next(ctx, cse); } 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 == CSE_MATCHER); cse->matcher.matcher->result = result; call_stack_drop(ctx); cse = apfl_call_stack_cur_entry(ctx); assert(cse != NULL); assert(cse->type == CSE_FUNCTION); } #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 void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { union matcher_instruction_or_arg arg; 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: if (!matcher_current_val(ctx, cse, &cur)) { RETURN_WITHOUT_MATCH(ctx); } must_get_matcher_argument(ctx, pc, milist, &arg); matcher_check_index(ctx, milist->capture_count, arg.index); matcher->captures[arg.index] = apfl_value_set_cow_flag(cur); RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse)); 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_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.feature_not_implemented); // TODO: Implement this goto continue_loop; 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 mode cse->matcher_stack_len == 1 && cse->matcher_stack[0].mode == MATCHER_MODE_STOP ); } struct apfl_iterative_runner_data { apfl_ctx ctx; apfl_tokenizer_ptr tokenizer; apfl_parser_ptr parser; enum apfl_result result; bool with_error_on_stack; bool end; 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 = CSE_FUNCTION, .stack = apfl_stack_new(), .func = { .pc = 0, .instructions = ilist, .scope = runner->scope, .closure_scope = NULL, .execution_line = ilist->line, .returning_from_matcher = false, .matcher = NULL, }, }); 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, .end = false, .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 handle_parse_error(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); } static void iterative_runner_next_protected(apfl_ctx ctx) { apfl_stack_drop(ctx, -1); apfl_cfunc_self_getslot(ctx, 0); apfl_iterative_runner runner = apfl_get_userdata(ctx, -1); 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: handle_parse_error(runner->ctx, apfl_parser_get_error(runner->parser)); return; case APFL_PARSE_EOF: runner->end = true; return; } assert(false); } bool apfl_iterative_runner_next(apfl_iterative_runner runner) { if (runner->end) { return false; } apfl_stack_clear(runner->ctx); // TODO: It's a bit silly that we build the cfunc every time. Can we be // smarter and leave it on the stack somewhere? Or save it in the // runner? Also, what if the construction of the func fails? We're not // running protected yet :/ apfl_push_cfunc(runner->ctx, iterative_runner_next_protected, 1); apfl_push_userdata(runner->ctx, runner); apfl_cfunc_setslot(runner->ctx, -2, 0, -1); apfl_list_create(runner->ctx, 0); apfl_call_protected(runner->ctx, -2, -1, &runner->with_error_on_stack); return !runner->end; } enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner runner) { return runner->result; } bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner runner) { return runner->with_error_on_stack; } 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)); } }