#include #include #include #include #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_allocator base_allocator) { apfl_ctx ctx = ALLOC_OBJ(base_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, base_allocator, get_roots, ctx); if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) { goto error; } 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; }