#include #include "apfl.h" #include "alloc.h" #include "bytecode.h" #include "compile.h" #include "context.h" #include "hashmap.h" #include "internal.h" #include "value.h" #define TRY(ex) \ do { \ enum apfl_result result = (ex); \ if (result != APFL_RESULT_OK) { \ return result; \ } \ } while (0) static enum apfl_result stack_push_or_fatal(apfl_ctx ctx, struct apfl_value value) { return apfl_stack_push(ctx, value) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static void stack_must_drop(apfl_ctx ctx, apfl_stackidx index) { assert(apfl_stack_drop(ctx, index)); } static enum apfl_result get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg) { if (*i >= ilist->len) { return APFL_RESULT_ERR; } *arg = ilist->instructions[(*i)++]; return APFL_RESULT_OK; } static enum apfl_result variable_get(apfl_ctx ctx, struct apfl_string *name) { struct apfl_value value; if (!apfl_scope_get(ctx->scope, name, &value)) { return APFL_RESULT_ERR; } return stack_push_or_fatal(ctx, value); } static enum apfl_result variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack) { struct apfl_value value; if (!apfl_stack_get(ctx, &value, -1)) { return APFL_RESULT_ERR; } if (!apfl_scope_set(&ctx->gc, ctx->scope, name, value)) { return APFL_RESULT_ERR_FATAL; } 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); } return APFL_RESULT_OK; } static enum apfl_result variable_new(apfl_ctx ctx, struct apfl_string *name) { if (!apfl_scope_create_var(&ctx->gc, ctx->scope, name)) { return APFL_RESULT_ERR_FATAL; } return APFL_RESULT_OK; } static enum apfl_result evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist) { union instruction_or_arg arg; assert(*i < ilist->len); switch (ilist->instructions[(*i)++].instruction) { case INSN_NIL: return stack_push_or_fatal(ctx, (struct apfl_value) {.type = VALUE_NIL}); case INSN_TRUE: return stack_push_or_fatal(ctx, (struct apfl_value) { .type = VALUE_BOOLEAN, .boolean = true, }); case INSN_FALSE: return stack_push_or_fatal(ctx, (struct apfl_value) { .type = VALUE_BOOLEAN, .boolean = true, }); case INSN_NUMBER: TRY(get_argument(i, ilist, &arg)); return stack_push_or_fatal(ctx, (struct apfl_value) { .type = VALUE_NUMBER, .number = arg.number, }); case INSN_STRING: TRY(get_argument(i, ilist, &arg)); return stack_push_or_fatal(ctx, (struct apfl_value) { .type = VALUE_STRING, .string = arg.string, }); case INSN_LIST: TRY(get_argument(i, ilist, &arg)); return apfl_list_create(ctx, arg.count); case INSN_LIST_APPEND: return apfl_list_append(ctx, -2, -1); case INSN_LIST_EXPAND_INTO: return apfl_list_append_list(ctx, -2, -1); case INSN_DICT: return apfl_dict_create(ctx); case INSN_DICT_APPEND_KVPAIR: return apfl_dict_set(ctx, -3, -2, -1); case INSN_GET_MEMBER: return apfl_get_member(ctx, -2, -1); case INSN_VAR_NEW: TRY(get_argument(i, ilist, &arg)); return variable_new(ctx, arg.string); case INSN_VAR_GET: TRY(get_argument(i, ilist, &arg)); return variable_get(ctx, arg.string); case INSN_VAR_SET: TRY(get_argument(i, ilist, &arg)); return variable_set(ctx, arg.string, true); case INSN_NEXT_LINE: ctx->execution_line++; return APFL_RESULT_OK; case INSN_SET_LINE: TRY(get_argument(i, ilist, &arg)); ctx->execution_line = arg.count; return APFL_RESULT_OK; } assert(false); return APFL_RESULT_ERR; } static enum apfl_result evaluate_list(apfl_ctx ctx, struct instruction_list *ilist) { ctx->execution_line = ilist->line; size_t i = 0; while (i < ilist->len) { TRY(evaluate(ctx, &i, ilist)); } return APFL_RESULT_OK; } static enum apfl_result eval_inner(apfl_ctx ctx, struct apfl_expr expr) { struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line); if (ilist == NULL) { return APFL_RESULT_ERR_FATAL; } if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) { return APFL_RESULT_ERR_FATAL; } struct apfl_error error; if (!apfl_compile(&ctx->gc, expr, &error, ilist)) { return APFL_RESULT_ERR; } return evaluate_list(ctx, ilist); } enum apfl_result apfl_eval(apfl_ctx ctx, struct apfl_expr expr) { // TODO: expr might have been allocated with another allocator. The apfl_ctx // should probably also handle parsing and no longer accept // expressions directly. size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); enum apfl_result result = eval_inner(ctx, expr); apfl_gc_tmproots_restore(&ctx->gc, tmproots); return result; } void apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, FILE *f) { struct apfl_value value; if (!apfl_stack_pop(ctx, &value, index)) { fprintf(f, "apfl_debug_print_val: Invalid stack index %d\n", index); return; } apfl_value_print(value, f); }