#include #include #include #include #include #include "apfl.h" #include "alloc.h" #include "builtins.h" #include "compile.h" #include "context.h" #include "gc.h" #include "hashmap.h" #include "modules.h" #include "registry.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); noreturn static void panic(apfl_ctx ctx, enum apfl_result result) { (void)ctx; (void)result; fprintf(stderr, "panic!\n"); // TODO: more details abort(); } struct protected_errcallback_data { void *opaque_outer; void (*errcallback)(apfl_ctx, void *); }; static void protected_errcallback(apfl_ctx ctx, void *opaque) { struct protected_errcallback_data *data = opaque; data->errcallback(ctx, data->opaque_outer); } static void protected_run_deferreds(apfl_ctx ctx, void *opaque) { struct call_stack_entry *cse = opaque; apfl_cfunc_run_deferred(ctx, cse); } static void protected_in_error_handling( apfl_ctx ctx, void (*callback)(apfl_ctx, void *), void *opaque, enum apfl_result *result, bool *with_error_on_stack ) { switch (apfl_do_protected(ctx, callback, opaque, NULL)) { case APFL_RESULT_OK: break; case APFL_RESULT_ERR: *result = APFL_RESULT_ERRERR; *with_error_on_stack = false; break; case APFL_RESULT_ERRERR: *result = APFL_RESULT_ERRERR; *with_error_on_stack = false; break; case APFL_RESULT_ERR_ALLOC: *result = APFL_RESULT_ERR_ALLOC; *with_error_on_stack = false; break; } } enum apfl_result apfl_do_protected( apfl_ctx ctx, void (*callback)(apfl_ctx, void *), void *opaque, void (*errcallback)(apfl_ctx, void *) ) { 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) { callback(ctx, opaque); } else { result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP); assert(result != APFL_RESULT_OK); bool with_error_on_stack = APFL_RESULT_ERR; if (with_error_on_stack && errcallback != NULL) { struct protected_errcallback_data data = { .opaque_outer = opaque, .errcallback = errcallback, }; protected_in_error_handling(ctx, protected_errcallback, &data, &result, &with_error_on_stack); } struct apfl_value err; if (with_error_on_stack && !apfl_stack_pop(ctx, &err, -1)) { result = APFL_RESULT_ERRERR; with_error_on_stack = false; } apfl_gc_tmproots_restore(&ctx->gc, tmproots); tmproots = apfl_gc_tmproots_begin(&ctx->gc); if (with_error_on_stack && !apfl_value_add_as_tmproot(&ctx->gc, err)) { result = APFL_RESULT_ERRERR; with_error_on_stack = false; } assert(callstack_len <= ctx->call_stack.cap); for (size_t i = ctx->call_stack.len; i-- > callstack_len; ) { struct call_stack_entry *cse = &ctx->call_stack.items[i]; if (cse->type == APFL_CSE_CFUNCTION) { protected_in_error_handling(ctx, protected_run_deferreds, cse, &result, &with_error_on_stack); } apfl_call_stack_entry_deinit(ctx->gc.allocator, cse); } bool ok = 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 ); assert(ok /* Shrinking should not fail */); if (with_error_on_stack && !apfl_stack_push(ctx, err)) { result = APFL_RESULT_ERRERR; with_error_on_stack = false; } apfl_gc_tmproots_restore(&ctx->gc, tmproots); } ctx->error_handler = prev_handler; return result; } struct call_protected_data { apfl_stackidx func; apfl_stackidx args; }; static void call_protected_cb(apfl_ctx ctx, void *opaque) { struct call_protected_data *data = opaque; apfl_call(ctx, data->func, data->args); } enum apfl_result apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args) { struct call_protected_data data = { .func = func, .args = args, }; return apfl_do_protected(ctx, call_protected_cb, &data, NULL); } noreturn static void raise_error(apfl_ctx ctx, enum apfl_result type) { assert(type != APFL_RESULT_OK); if (ctx->error_handler != NULL) { longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP); } if (ctx->panic_callback != NULL) { ctx->panic_callback(ctx, ctx->panic_callback_data, type); } panic(ctx, type); } noreturn void apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx) { if (!current_stack_move_to_top(ctx, idx)) { raise_error(ctx, APFL_RESULT_ERRERR); } raise_error(ctx, APFL_RESULT_ERR); } noreturn void apfl_raise_const_error(apfl_ctx ctx, const char *message) { if (!try_push_const_string(ctx, message)) { raise_error(ctx, APFL_RESULT_ERRERR); } raise_error(ctx, APFL_RESULT_ERR); } noreturn void apfl_raise_alloc_error(apfl_ctx ctx) { raise_error(ctx, APFL_RESULT_ERR_ALLOC); } noreturn void apfl_raise_invalid_stackidx(apfl_ctx ctx) { apfl_raise_const_error(ctx, apfl_messages.invalid_stack_index); } noreturn void apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error) { if (error.type == APFL_ERR_MALLOC_FAILED) { apfl_raise_alloc_error(ctx); } const char *const_str = apfl_error_as_const_string(error); if (const_str != NULL) { apfl_raise_const_error(ctx, 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(ctx, -1); } struct errorfmt_data { va_list ap; apfl_ctx ctx; }; static bool errorfmt_str(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg) { struct errorfmt_data *data = opaque; struct apfl_string_view sv; if (apfl_string_eq(arg, "c")) { char *cstr = va_arg(data->ap, char *); sv = apfl_string_view_from(cstr); } else { sv = va_arg(data->ap, struct apfl_string_view); } return apfl_io_write_string_view(w, sv); } static bool errorfmt_type(void *opaque, struct apfl_io_writer w, struct apfl_string_view arg) { struct errorfmt_data *data = opaque; enum apfl_value_type type; if (apfl_string_eq(arg, "stack")) { apfl_stackidx i = va_arg(data->ap, apfl_stackidx); struct apfl_value val; if (!apfl_stack_get(data->ctx, &val, i)) { FMT_TRY(apfl_io_write_string(w, "{stack:type => invalid stack index}")); return true; } type = apfl_value_type_to_abstract_type(val.type); } else if (apfl_string_eq(arg, "value")) { struct apfl_value val = va_arg(data->ap, struct apfl_value); type = apfl_value_type_to_abstract_type(val.type); } else { type = va_arg(data->ap, enum apfl_value_type); } return apfl_io_write_string_view(w, apfl_string_view_from(apfl_type_name(type))); } static const struct format_spec errorfmt_specs[] = { {"string", errorfmt_str}, {"type", errorfmt_type}, {NULL, NULL}, }; noreturn void apfl_raise_errorfmt(apfl_ctx ctx, const char *fmt, ...) { struct errorfmt_data data = { .ctx = ctx, }; va_start(data.ap, fmt); struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); struct apfl_io_writer w = apfl_io_string_writer(&sb); bool result = apfl_abstract_format(&data, errorfmt_specs, w, apfl_string_view_from(fmt)); va_end(data.ap); if (!result) { apfl_string_builder_deinit(&sb); apfl_raise_alloc_error(ctx); } 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); } apfl_raise_error(ctx, -1); } 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; ) { bool ok = apfl_resizable_cut_without_resize( sizeof(struct apfl_value), (void **)&stack->items, &stack->len, indices[i], 1 ); assert(ok /* Will not fail, as we've already checked the indices */); } // 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]; bool ok = apfl_resizable_splice( ctx->gc.allocator, sizeof(struct apfl_value), (void **)&stack->items, &stack->len, &stack->cap, index, 1, NULL, 0 ); assert(ok /* Shrinking shouldn't fail */); 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); } } bool current_stack_multi_move_to_top(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; } } for (size_t i = 0; i < count; i++) { // If we're here, index is an absolute address and is guaranteed to be < len assert(indices[i] >= 0); size_t absindex = (size_t)indices[i]; assert(stack->len >= absindex+1); struct apfl_value val = stack->items[absindex]; memmove( &stack->items[absindex], &stack->items[absindex + 1], sizeof(struct apfl_value) * (stack->len - absindex - 1) ); stack->items[stack->len-1] = val; for (size_t j = i + 1; j < count; j++) { if (indices[j] >= indices[i]) { indices[j]--; } } } return true; } static bool current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index) { return current_stack_multi_move_to_top(ctx, 1, &index); } void apfl_move_to_top_of_stack(apfl_ctx ctx, apfl_stackidx index) { if (!current_stack_move_to_top(ctx, index)) { apfl_raise_invalid_stackidx(ctx); } } void apfl_multi_move_to_top_of_stack(apfl_ctx ctx, size_t count, apfl_stackidx *indices) { if (!current_stack_multi_move_to_top(ctx, count, indices)) { apfl_raise_invalid_stackidx(ctx); } } void apfl_copy(apfl_ctx ctx, apfl_stackidx index) { struct apfl_value value = apfl_stack_must_get(ctx, index); apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value)); } 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++) { apfl_value_visit_gc_object(stack.items[i], visitor, opaque); } } 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 visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque) { visit_nullable_scope(scopes.local, visitor, opaque); visit_nullable_scope(scopes.closure, visitor, opaque); } static void visit_matcher_stack(struct matcher_stack matcher_stack, gc_visitor visitor, void *opaque) { for (size_t i = 0; i < matcher_stack.len; i++) { visitor( opaque, GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER) ); } } static void visit_func_cse(struct func_call_stack_entry cse, gc_visitor visitor, void *opaque) { visitor( opaque, GC_OBJECT_FROM(cse.instructions, GC_TYPE_INSTRUCTIONS) ); visit_scopes(cse.scopes, visitor, opaque); visit_matcher_stack(cse.matcher_stack, visitor, opaque); } static void visit_cfunc_cse(struct cfunc_call_stack_entry cse, gc_visitor visitor, void *opaque) { visitor( opaque, GC_OBJECT_FROM(cse.func, GC_TYPE_CFUNC) ); } static void visit_matcher_cse(struct matcher_call_stack_entry cse, gc_visitor visitor, void* opaque) { visitor( opaque, GC_OBJECT_FROM(cse.matcher, GC_TYPE_MATCHER) ); visit_scopes(cse.scopes, visitor, opaque); for (size_t i = 0; i < cse.capture_count; i++) { apfl_value_visit_gc_object(cse.captures[i], visitor, opaque); visitor( opaque, GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING) ); } } static void visit_func_dispatch_cse(struct func_dispatch_call_stack_entry cse, gc_visitor visitor, void *opaque) { visitor( opaque, GC_OBJECT_FROM(cse.function, GC_TYPE_FUNC) ); visit_scopes(cse.scopes, visitor, opaque); } 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 APFL_CSE_FUNCTION: visit_func_cse(cse.func, visitor, opaque); break; case APFL_CSE_CFUNCTION: visit_cfunc_cse(cse.cfunc, visitor, opaque); break; case APFL_CSE_MATCHER: visit_matcher_cse(cse.matcher, visitor, opaque); break; case APFL_CSE_FUNCTION_DISPATCH: visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque); 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); } apfl_registry_visit_gc_objects(ctx->registry, 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(); } static void func_call_stack_entry_deinit(struct apfl_allocator allocator, struct func_call_stack_entry *cse) { FREE_LIST(allocator, cse->matcher_stack.items, cse->matcher_stack.cap); } static void cfunc_call_stack_entry_deinit(struct apfl_allocator allocator, struct cfunc_call_stack_entry *cse) { FREE_LIST(allocator, cse->deferred_list, cse->deferred_cap); } void apfl_matcher_call_stack_entry_deinit(struct apfl_allocator allocator, struct matcher_call_stack_entry *cse) { FREE_LIST(allocator, cse->captures, cse->capture_count); cse->captures = NULL; FREE_LIST(allocator, cse->transfers, cse->capture_count); cse->transfers = NULL; FREE_LIST(allocator, cse->matcher_state_stack, cse->matcher_state_stack_cap); cse->matcher_state_stack = NULL; } void apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry) { deinit_stack(allocator, &entry->stack); switch (entry->type) { case APFL_CSE_FUNCTION: func_call_stack_entry_deinit(allocator, &entry->func); break; case APFL_CSE_CFUNCTION: cfunc_call_stack_entry_deinit(allocator, &entry->cfunc); break; case APFL_CSE_FUNCTION_DISPATCH: break; case APFL_CSE_MATCHER: apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher); 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 void init_globals_protected(apfl_ctx ctx, void *opaque) { (void)opaque; apfl_builtins(ctx); apfl_build_native_and_bytecode_combined_module(ctx, -1, apfl_mod_globals()); struct apfl_value val = apfl_stack_must_get(ctx, -1); if (val.type != VALUE_DICT) { apfl_raise_const_error(ctx, "Globals didn't return a dict"); } HASHMAP_EACH(&val.dict->map, cur) { struct apfl_value *k = apfl_hashmap_cursor_peek_key(cur); apfl_stack_must_push(ctx, *k); struct apfl_string *name = apfl_to_dynamic_string(ctx, -1); struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur); if (!apfl_scope_set(&ctx->gc, ctx->globals, name, *v)) { apfl_raise_alloc_error(ctx); } apfl_drop(ctx, -1); } } static void init_standard_modules_protected(apfl_ctx ctx, void *opaque) { (void)opaque; apfl_push_cfunc(ctx, apfl_module_re, 0); apfl_modules_register(ctx, "re", -1); } #define DEBUG_INIT_GLOBALS 1 #if DEBUG_INIT_GLOBALS static void on_init_globals_error(apfl_ctx ctx, void *opaque) { (void)opaque; struct apfl_io_writer w = apfl_io_file_writer(stderr); apfl_tostring(ctx, -1); if (!( apfl_io_write_string(w, apfl_get_string(ctx, -1)) && apfl_io_write_string(w, "\n\nBacktrace:") )) { apfl_raise_const_error(ctx, "Error in on_init_globals_error"); } size_t depth = apfl_call_stack_depth(ctx); for (size_t i = 0; i < depth; i++) { if (!( apfl_io_write_string(w, "\n") && apfl_io_write_string(w, "#") && apfl_format_put_int(w, (int)i+1) && apfl_io_write_string(w, ": ") && apfl_call_stack_entry_info_format( w, apfl_call_stack_inspect(ctx, i) ) )) { apfl_raise_const_error(ctx, "Error in on_init_globals_error"); } } } #define INIT_GLOBALS_ERRCALLBACK on_init_globals_error #else #define INIT_GLOBALS_ERRCALLBACK NULL #endif static bool init_globals(apfl_ctx ctx) { return apfl_do_protected(ctx, init_globals_protected, NULL, INIT_GLOBALS_ERRCALLBACK) == APFL_RESULT_OK; } static bool init_standard_modules(apfl_ctx ctx) { return apfl_do_protected(ctx, init_standard_modules_protected, NULL, INIT_GLOBALS_ERRCALLBACK) == APFL_RESULT_OK; } apfl_ctx apfl_ctx_new(struct apfl_config config) { apfl_ctx ctx = ALLOC_OBJ(config.allocator, struct apfl_ctx_data); if (ctx == NULL) { return NULL; } // It's important that we initialize all these before initializing the // garbage collector, otherwise it might try to follow invalid pointers. ctx->error_handler = NULL; ctx->panic_callback = NULL; ctx->panic_callback_data = NULL; ctx->toplevel_stack = apfl_stack_new(); ctx->call_stack = call_stack_new(); ctx->globals = NULL; ctx->iterative_runners = iterative_runners_list_new(); ctx->registry = NULL; apfl_gc_init(&ctx->gc, config.allocator, get_roots, ctx); if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) { goto error; } ctx->output_writer = config.output_writer; if ((ctx->registry = apfl_registry_new(ctx->gc.allocator)) == NULL) { goto error; } if (!init_globals(ctx)) { goto error; } if (!config.no_standard_modules && !init_standard_modules(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(); apfl_registry_destroy(ctx->registry); ctx->registry = NULL; 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; } bool ok = 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 ); assert( // We're only removing elements, the buffer should not grow, // therefore there should be no allocation errors ok ); } #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) { \ bool ok = apfl_stack_drop(ctx, -1); \ assert(ok); \ 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, }); } void apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv) { CREATE_GC_OBJECT_VALUE_ON_STACK( ctx, VALUE_STRING, string, apfl_string_copy_to_new_gc_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_errorfmt( ctx, "Can not append to value of type {value:type}, expected list", *list_val ); } struct apfl_value value = apfl_stack_must_get(ctx, value_index); bool ok; if (!apfl_list_splice_raw( &ctx->gc, &list_val->list, list_val->list->len, 0, &value, 1 )) { ok = apfl_stack_drop(ctx, value_index); assert(ok); apfl_raise_alloc_error(ctx); } ok = apfl_stack_drop(ctx, value_index); assert(ok); } 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_messages.not_a_list); } struct apfl_value src_val = apfl_stack_must_get(ctx, src_index); bool ok; if (src_val.type != VALUE_LIST) { ok = apfl_stack_drop(ctx, src_index); assert(ok); apfl_raise_const_error(ctx, apfl_messages.not_a_list); } if (!apfl_list_splice_raw( &ctx->gc, &dst_val->list, dst_val->list->len, 0, src_val.list->items, src_val.list->len )) { apfl_raise_alloc_error(ctx); } ok = apfl_stack_drop(ctx, src_index); assert(ok); } 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); } bool ok; if (dict_value->type != VALUE_DICT) { ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}); assert(ok); apfl_raise_const_error(ctx, apfl_messages.not_a_dict); } if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) { ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}); assert(ok); apfl_raise_alloc_error(ctx); } ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}); assert(ok); } 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 push_pair_inner(apfl_ctx ctx, apfl_stackidx l_index, apfl_stackidx r_index) { struct apfl_value l = apfl_stack_must_get(ctx, l_index); struct apfl_value r = apfl_stack_must_get(ctx, r_index); must_tmproot_add_value(ctx, l); must_tmproot_add_value(ctx, r); bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){l_index, r_index}); assert(ok); CREATE_GC_OBJECT_VALUE_ON_STACK( ctx, VALUE_PAIR, pair, apfl_pair_new(&ctx->gc, l, r) ) } void apfl_push_pair(apfl_ctx ctx, apfl_stackidx l, apfl_stackidx r) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); push_pair_inner(ctx, l, r); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } void apfl_push_csymbol(apfl_ctx ctx, apfl_cfunc id, const char *name) { apfl_stack_must_push(ctx, (struct apfl_value) { .type = VALUE_CSYMBOL, .csymbol = { .id = id, .name = name, }, }); } apfl_cfunc apfl_pop_csymbol(apfl_ctx ctx, apfl_stackidx idx) { struct apfl_value val = apfl_stack_must_pop(ctx, idx); return val.type == VALUE_CSYMBOL ? val.csymbol.id : NULL; } static void push_symbol_inner(apfl_ctx ctx, apfl_stackidx idx) { apfl_move_to_top_of_stack(ctx, idx); if (apfl_get_type(ctx, -1) != APFL_VALUE_STRING) { apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", -1); } struct apfl_string *name = apfl_to_dynamic_string(ctx, -1); if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) { apfl_raise_alloc_error(ctx); } apfl_stack_drop(ctx, -1); CREATE_GC_OBJECT_VALUE_ON_STACK( ctx, VALUE_SYMBOL, symbol, apfl_symbol_new(&ctx->gc, name) ) } void apfl_push_symbol(apfl_ctx ctx, apfl_stackidx idx) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); push_symbol_inner(ctx, idx); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } void apfl_push_anon_symbol(apfl_ctx ctx) { CREATE_GC_OBJECT_VALUE_ON_STACK( ctx, VALUE_SYMBOL, symbol, apfl_symbol_new(&ctx->gc, NULL) ) } bool apfl_get_member_if_exists( 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); bool ok; if (value == NULL) { ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}); assert(ok); apfl_raise_alloc_error(ctx); } enum get_item_result result = apfl_value_get_item(container, k, value); if (result != GET_ITEM_OK) { ok = apfl_stack_drop(ctx, -1); // Drop the placeholder assert(ok); } ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}); assert(ok); switch (result) { case GET_ITEM_OK: return true; case GET_ITEM_KEY_DOESNT_EXIST: return false; case GET_ITEM_NOT_A_CONTAINER: apfl_raise_errorfmt( ctx, "Can not get member of value of type {value:type}, not a container", container ); break; case GET_ITEM_WRONG_KEY_TYPE: apfl_raise_errorfmt( ctx, "Value of type {value:type} is not a valid key for container type {value:type}", k, container ); break; } assert(false); return false; } void apfl_get_member( apfl_ctx ctx, apfl_stackidx container_index, apfl_stackidx k_index ) { if (!apfl_get_member_if_exists(ctx, container_index, k_index)) { apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist); } } 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_messages.not_a_list); } if (index >= list.list->len) { apfl_raise_const_error(ctx, 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]); } void apfl_set_list_member_by_index( apfl_ctx ctx, apfl_stackidx list_index, size_t index_in_list, apfl_stackidx value_index ) { struct apfl_value *list = stack_get_pointer(ctx, list_index); if (list == NULL) { apfl_raise_invalid_stackidx(ctx); } if (list->type != VALUE_LIST) { apfl_raise_const_error(ctx, apfl_messages.not_a_list); } if (index_in_list >= list->list->len) { apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist); } struct apfl_value value = apfl_stack_must_get(ctx, value_index); if (!apfl_list_splice_raw( &ctx->gc, &list->list, index_in_list, 1, &value, 1 )) { apfl_raise_alloc_error(ctx); } apfl_drop(ctx, value_index); } void apfl_list_splice( apfl_ctx ctx, apfl_stackidx list, size_t cut_off, size_t cut_count, apfl_stackidx other, size_t other_off, size_t other_count ) { apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]) {other, list}); struct apfl_value *list_val = stack_get_pointer(ctx, -1); struct apfl_value *other_val = stack_get_pointer(ctx, -2); if (list_val->type != VALUE_LIST) { apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *list_val); } struct list_header** list_listp = &list_val->list; if (other_val->type != VALUE_LIST) { apfl_raise_errorfmt(ctx, "Expected list, got {value:type}", *other_val); } struct list_header* other_list = other_val->list; if (!apfl_resizable_check_cut_args((*list_listp)->len, cut_off, cut_count)) { apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range); } struct apfl_value *other_values = NULL; if (other_count > 0) { if ( other_off >= other_list->len || other_count > (other_list->len - other_off) ) { apfl_raise_const_error(ctx, apfl_messages.splice_arguments_out_of_range); } other_values = &(other_list->items[other_off]); } if (!apfl_list_splice_raw( &ctx->gc, list_listp, cut_off, cut_count, other_values, other_count )) { apfl_raise_alloc_error(ctx); } apfl_drop(ctx, -2); } 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_PAIR: case VALUE_FUNC: case VALUE_CFUNC: case VALUE_USERDATA: case VALUE_NATIVE_OBJECT: case VALUE_SYMBOL: case VALUE_CSYMBOL: apfl_raise_errorfmt( ctx, "Can not get length of value of type {value:type}", value ); 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; } static void iterate_dict_inner(apfl_ctx ctx, apfl_stackidx dict_index, void *opaque, bool (*callback)(apfl_ctx, void *)) { struct apfl_value dict_value = apfl_value_set_cow_flag(apfl_stack_must_pop(ctx, dict_index)); if (!apfl_value_add_as_tmproot(&ctx->gc, dict_value)) { apfl_raise_alloc_error(ctx); } if (dict_value.type != VALUE_DICT) { apfl_raise_errorfmt(ctx, "Expected dict value, got {value:type}", dict_value); } HASHMAP_EACH(&(dict_value.dict->map), it) { struct apfl_value *k = apfl_hashmap_cursor_peek_key(it); apfl_stack_must_push(ctx, *k); struct apfl_value *v = apfl_hashmap_cursor_peek_value(it); apfl_stack_must_push(ctx, *v); if (!callback(ctx, opaque)) { break; } } } void apfl_iterate_dict(apfl_ctx ctx, apfl_stackidx dict, void *opaque, bool (*it)(apfl_ctx, void *)) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); iterate_dict_inner(ctx, dict, opaque, it); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } static bool get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value) { 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: case VALUE_PAIR: case VALUE_NATIVE_OBJECT: case VALUE_SYMBOL: case VALUE_CSYMBOL: return false; case VALUE_STRING: *sv = apfl_string_view_from(*value.string); return true; case VALUE_CONST_STRING: *sv = value.const_string; return true; } assert(false); return false; } struct apfl_string_view apfl_get_string(apfl_ctx ctx, apfl_stackidx index) { struct apfl_string_view sv; if (!get_string_view_of_value(&sv, apfl_stack_must_get(ctx, index))) { apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", index); } return sv; } 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); } void apfl_tostring(apfl_ctx ctx, apfl_stackidx index) { apfl_move_to_top_of_stack(ctx, index); struct apfl_value value = apfl_stack_must_get(ctx, -1); if (apfl_value_type_to_abstract_type(value.type) == APFL_VALUE_STRING) { return; } struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); if (!apfl_value_format(value, apfl_io_string_writer(&sb))) { apfl_string_builder_deinit(&sb); apfl_stack_drop(ctx, -1); apfl_raise_alloc_error(ctx); } struct apfl_string str = apfl_string_builder_move_string(&sb); apfl_string_builder_deinit(&sb); if (!apfl_move_string_onto_stack(ctx, str)) { apfl_string_deinit(ctx->gc.allocator, &str); apfl_stack_drop(ctx, -1); apfl_raise_alloc_error(ctx); } apfl_stack_drop(ctx, -2); // Drop original value } struct apfl_string * apfl_to_dynamic_string(apfl_ctx ctx, apfl_stackidx index) { apfl_tostring(ctx, index); struct apfl_value value = apfl_stack_must_get(ctx, -1); if (value.type == VALUE_STRING) { return value.string; } assert(value.type == VALUE_CONST_STRING /* apfl_tostring results in either VALUE_STRING or VALUE_CONST_STRING */); apfl_push_string_view_copy(ctx, value.const_string); value = apfl_stack_must_get(ctx, -1); apfl_drop(ctx, -2); return value.string; } void apfl_join_strings(apfl_ctx ctx, apfl_stackidx glue, apfl_stackidx parts) { apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){parts, glue}); parts = -2; glue = -1; if (apfl_get_type(ctx, parts) != APFL_VALUE_LIST) { apfl_raise_const_error(ctx, apfl_messages.not_a_list); } size_t parts_len = apfl_len(ctx, parts); apfl_tostring(ctx, glue); struct apfl_string_view glue_sv = apfl_get_string(ctx, glue); size_t total_length = 0; for (size_t i = 0; i < parts_len; i++) { // Convert values to strings, if necessary apfl_get_list_member_by_index(ctx, parts, i); if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) { total_length += apfl_get_string(ctx, -1).len; apfl_drop(ctx, -1); } else { apfl_tostring(ctx, -1); total_length += apfl_get_string(ctx, -1).len; apfl_set_list_member_by_index(ctx, -3, i, -1); } if (i > 0) { total_length += glue_sv.len; } } struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); if (!apfl_string_builder_prealloc(&sb, total_length)) { goto error; } for (size_t i = 0; i < parts_len; i++) { if (i > 0) { if (!apfl_string_builder_append(&sb, glue_sv)) { goto error; } } apfl_get_list_member_by_index(ctx, parts, i); if (!apfl_string_builder_append(&sb, apfl_get_string(ctx, -1))) { goto error; } apfl_drop(ctx, -1); } struct apfl_string str = apfl_string_builder_move_string(&sb); apfl_string_builder_deinit(&sb); if (!apfl_move_string_onto_stack(ctx, str)) { apfl_string_deinit(ctx->gc.allocator, &str); goto error; } // Drop the glue and list from the stack apfl_drop(ctx, -2); apfl_drop(ctx, -2); return; error: // Drop the glue and list from the stack apfl_drop(ctx, -1); apfl_drop(ctx, -1); apfl_raise_alloc_error(ctx); } void apfl_concat_strings(apfl_ctx ctx, apfl_stackidx a, apfl_stackidx b) { apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){a, b}); apfl_tostring(ctx, -2); apfl_tostring(ctx, -2); struct apfl_string_view sv_a = apfl_get_string(ctx, -2); struct apfl_string_view sv_b = apfl_get_string(ctx, -1); struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); if (!apfl_string_builder_prealloc(&sb, sv_a.len + sv_b.len)) { goto error; } if ( !apfl_string_builder_append(&sb, sv_a) || !apfl_string_builder_append(&sb, sv_b) ) { goto error; } struct apfl_string str = apfl_string_builder_move_string(&sb); apfl_string_builder_deinit(&sb); if (!apfl_move_string_onto_stack(ctx, str)) { apfl_string_deinit(ctx->gc.allocator, &str); goto error; } apfl_drop(ctx, -2); apfl_drop(ctx, -2); return; error: apfl_drop(ctx, -1); apfl_drop(ctx, -1); apfl_raise_alloc_error(ctx); } bool apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index) { struct apfl_value value = apfl_stack_must_pop(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_errorfmt(ctx, "Expected number, got {value:type}", value); } 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); bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}); assert(ok); return eq; } int apfl_cmp(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); enum comparison_result result = apfl_value_cmp(a, b); bool ok = apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}); assert(ok); switch (result) { case CMP_LT: return -1; case CMP_EQ: return 0; case CMP_GT: return 1; case CMP_UNCOMPARABLE: apfl_raise_errorfmt(ctx, "Values of type {value:type} can not be compared", a); case CMP_INCOMPATIBLE_TYPES: apfl_raise_errorfmt(ctx, "Can not compare values of incompatible types {value:type} and {value:type}", a, b); } assert(false); return 0; } static struct scope * closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes) { 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; } // The order is important here: by merging the local scope last, we make // sure a variable from the current scope shadows a variable from the // closure scope if (scopes.closure != NULL) { if (!apfl_scope_merge_into(out, scopes.closure)) { return NULL; } } if (scopes.local != NULL) { if (!apfl_scope_merge_into(out, scopes.local)) { return NULL; } } return out; } struct scope * apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); struct scope *out = closure_scope_for_func_inner(ctx, scopes); 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) ) } void apfl_set_func_name(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx name_index) { apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx []) {func_index, name_index}); if (apfl_get_type(ctx, -1) != APFL_VALUE_STRING) { apfl_raise_errorfmt(ctx, "Expected string, got {stack:type}", -1); } struct apfl_value func = apfl_stack_must_get(ctx, -2); struct apfl_string *name = apfl_to_dynamic_string(ctx, -1); switch (func.type) { case VALUE_FUNC: func.func->name = name; break; case VALUE_CFUNC: func.cfunc->name = name; break; default: apfl_raise_errorfmt(ctx, "Expected function, got {value:type}", func); } apfl_drop(ctx, -1); } static noreturn void raise_no_cfunc(apfl_ctx ctx) { apfl_raise_const_error(ctx, 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 != APFL_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_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_messages.wrong_type); } return value.userdata; } void * apfl_push_native_object(apfl_ctx ctx, const struct apfl_native_object_type *type) { CREATE_GC_OBJECT_VALUE_ON_STACK( ctx, VALUE_NATIVE_OBJECT, native_object, apfl_native_object_new(&ctx->gc, type) ) return apfl_get_native_object(ctx, type, -1); } void * apfl_get_native_object(apfl_ctx ctx, const struct apfl_native_object_type *type, apfl_stackidx index) { struct apfl_value value = apfl_stack_must_get(ctx, index); if (value.type != VALUE_NATIVE_OBJECT || value.native_object->type != type) { apfl_raise_const_error(ctx, apfl_messages.wrong_type); } return value.native_object->memory; } struct apfl_io_writer apfl_get_output_writer(apfl_ctx ctx) { return ctx->output_writer; } static bool init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len) { if (len == 0) { return true; } if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) { return false; } for (size_t i = 0; i < len; i++) { (*list)[i] = (struct apfl_value) { .type = VALUE_NIL }; } return true; } struct matcher * apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist) { struct matcher matcher = { .instructions = milist, .value_count = 0, .values = NULL, }; if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) { goto error; } matcher.value_count = milist->value_count; struct matcher *gc_matcher = apfl_gc_new_matcher(gc); if (gc_matcher == NULL) { goto error; } *gc_matcher = matcher; return gc_matcher; error: apfl_matcher_deinit(gc->allocator, &matcher); return NULL; } void apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher) { FREE_LIST(allocator, matcher->values, matcher->value_count); } void apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque) { visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS)); for (size_t i = 0; i < matcher->instructions->value_count; i++) { apfl_value_visit_gc_object(matcher->values[i], visitor, opaque); } } size_t apfl_call_stack_depth(apfl_ctx ctx) { return ctx->call_stack.len; } static struct apfl_string_view get_string_view_or_empty(struct apfl_string *s) { return s == NULL ? (struct apfl_string_view) { .len = 0, .bytes = NULL, } : apfl_string_view_from(*s); } struct apfl_call_stack_entry_info apfl_call_stack_inspect(apfl_ctx ctx, size_t n) { if (n >= ctx->call_stack.len) { apfl_raise_const_error(ctx, apfl_messages.invalid_call_stack_index); } struct call_stack_entry *cse = &ctx->call_stack.items[ctx->call_stack.len - n - 1]; struct apfl_call_stack_entry_info info = { .type = cse->type, .name = (struct apfl_string_view) { .len = 0, .bytes = NULL, }, .filename = (struct apfl_string_view) { .len = 0, .bytes = NULL, }, .toplevel = false, }; switch (cse->type) { case APFL_CSE_FUNCTION: info.line_current = cse->func.execution_line; info.filename = get_string_view_or_empty(cse->func.instructions->filename); if (cse->func.function == NULL) { info.toplevel = true; } else { info.line_defined = cse->func.instructions->line; info.subfunction_index = cse->func.subfunction_index; struct apfl_string_view sv; if (apfl_func_get_name(cse->func.function, &sv)) { info.name = sv; } } break; case APFL_CSE_CFUNCTION: info.name = get_string_view_or_empty(cse->cfunc.func->name); break; case APFL_CSE_FUNCTION_DISPATCH: { struct apfl_string_view sv; if (apfl_func_get_name(cse->func_dispatch.function, &sv)) { info.name = sv; } info.line_defined = cse->func_dispatch.function->line_defined; info.filename = get_string_view_or_empty(cse->func_dispatch.function->filename); break; } case APFL_CSE_MATCHER: break; } return info; } static bool format_defined_at( struct apfl_io_writer w, struct apfl_call_stack_entry_info info, struct apfl_string_view *filename ) { FMT_TRY(apfl_io_write_string(w, "defined in ")); if (filename != NULL && filename->len > 0) { FMT_TRY(apfl_io_write_string(w, "file ")); FMT_TRY(apfl_io_write_string(w, *filename)); FMT_TRY(apfl_io_write_string(w, ", ")); } FMT_TRY(apfl_io_write_string(w, "line ")); FMT_TRY(apfl_format_put_int(w, (int)info.line_defined)); return true; } bool apfl_call_stack_entry_info_format(struct apfl_io_writer w, struct apfl_call_stack_entry_info info) { switch (info.type) { case APFL_CSE_FUNCTION: if (info.filename.len > 0) { FMT_TRY(apfl_io_write_string(w, "File ")); FMT_TRY(apfl_io_write_string(w, info.filename)); FMT_TRY(apfl_io_write_string(w, ", ")); } FMT_TRY(apfl_io_write_string(w, "Line ")); FMT_TRY(apfl_format_put_int(w, (int)info.line_current)); FMT_TRY(apfl_io_write_string(w, ", ")); if (info.toplevel) { FMT_TRY(apfl_io_write_string(w, "toplevel ")); } else if (info.name.len == 0) { FMT_TRY(apfl_io_write_string(w, "anonymous function (subfunction ")); FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index)); FMT_TRY(apfl_io_write_string(w, "; ")); FMT_TRY(format_defined_at(w, info, NULL)); FMT_TRY(apfl_io_write_string(w, ")")); } else { FMT_TRY(apfl_io_write_string(w, "function ")); FMT_TRY(apfl_io_write_string(w, info.name)); FMT_TRY(apfl_io_write_string(w, " (subfunction ")); FMT_TRY(apfl_format_put_int(w, (int)info.subfunction_index)); FMT_TRY(apfl_io_write_string(w, "; ")); FMT_TRY(format_defined_at(w, info, NULL)); FMT_TRY(apfl_io_write_string(w, ")")); } break; case APFL_CSE_CFUNCTION: if (info.name.len == 0) { FMT_TRY(apfl_io_write_string(w, "Anonymous native function")); } else { FMT_TRY(apfl_io_write_string(w, "Native function ")); FMT_TRY(apfl_io_write_string(w, info.name)); } break; case APFL_CSE_FUNCTION_DISPATCH: FMT_TRY(apfl_io_write_string(w, "Dispatch for ")); if (info.name.len == 0) { FMT_TRY(apfl_io_write_string(w, "anonymous function (")); } else { FMT_TRY(apfl_io_write_string(w, "function ")); FMT_TRY(apfl_io_write_string(w, info.name)); FMT_TRY(apfl_io_write_string(w, "(")); } FMT_TRY(format_defined_at(w, info, &info.filename)); FMT_TRY(apfl_io_write_string(w, ")")); break; case APFL_CSE_MATCHER: FMT_TRY(apfl_io_write_string(w, "Matcher")); break; } return true; } static struct instruction_list * setup_function_for_load_inner(apfl_ctx ctx, struct apfl_string *filename) { struct apfl_value *func_value = apfl_stack_push_placeholder(ctx); if (func_value == NULL) { return NULL; } if ((func_value->func = apfl_func_new(&ctx->gc, 1, NULL, 1, filename)) == NULL) { apfl_drop(ctx, -1); return NULL; } func_value->type = VALUE_FUNC; struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, 1, filename); if ( ilist == NULL || !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS)) ) { return NULL; } struct matcher_instruction_list *milist = apfl_matcher_instructions_new(&ctx->gc); if ( milist == NULL || !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(milist, GC_TYPE_MATCHER_INSTRUCTIONS)) ) { return NULL; } if (!apfl_resizable_append( ctx->gc.allocator, sizeof(union matcher_instruction_or_arg), (void **)&milist->instructions, &milist->len, &milist->cap, &(union matcher_instruction_or_arg[]) { {.instruction = MATCHER_IGNORE}, }, 1 )) { return NULL; } struct matcher *matcher = apfl_matcher_new(&ctx->gc, milist); if ( matcher == NULL || !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER)) ) { apfl_drop(ctx, -1); return NULL; } if (!apfl_func_add_subfunc(func_value->func, ilist, matcher)) { apfl_drop(ctx, -1); return NULL; } return ilist; } static struct instruction_list * setup_function_for_load(apfl_ctx ctx, struct apfl_string *filename) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); struct instruction_list *out = setup_function_for_load_inner(ctx, filename); apfl_gc_tmproots_restore(&ctx->gc, tmproots); return out; } void apfl_load(apfl_ctx ctx, struct apfl_source_reader reader, apfl_stackidx name) { struct apfl_string *filename = apfl_to_dynamic_string(ctx, name); apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader); if (tokenizer == NULL) { apfl_raise_alloc_error(ctx); } apfl_parser_ptr parser = apfl_parser_new( ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer) ); if (parser == NULL) { apfl_tokenizer_destroy(tokenizer); apfl_raise_alloc_error(ctx); } struct instruction_list *ilist = setup_function_for_load(ctx, filename); if (ilist == NULL) { apfl_parser_destroy(parser); apfl_tokenizer_destroy(tokenizer); apfl_raise_alloc_error(ctx); } struct apfl_error err; if (!apfl_compile_whole_file( &ctx->gc, parser, &err, ilist )) { apfl_drop(ctx, -1); apfl_parser_destroy(parser); apfl_tokenizer_destroy(tokenizer); apfl_raise_error_object(ctx, err); } } static void load_bytecode_inner(apfl_ctx ctx, struct apfl_io_reader r) { struct instruction_list *ilist = apfl_bytecode_unserialize(&ctx->gc, r); if (ilist == NULL) { apfl_raise_const_error(ctx, "Failed to load bytecode"); } if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) { 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, 1, NULL, ilist->line, ilist->filename )) == NULL) { apfl_drop(ctx, -1); apfl_raise_alloc_error(ctx); } func_value->type = VALUE_FUNC; bool ok = apfl_func_add_subfunc(func_value->func, ilist, NULL); assert(ok /* should not fail, func was initialized with cap of 1 */); } void apfl_load_bytecode(apfl_ctx ctx, struct apfl_io_reader r) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); load_bytecode_inner(ctx, r); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } struct apfl_allocator apfl_get_allocator(apfl_ctx ctx) { return ctx->gc.allocator; } void apfl_cfunc_defer(apfl_ctx ctx, apfl_cfunc_defer_callback cb, void *opaque) { struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); if (cse == NULL || cse->type != APFL_CSE_CFUNCTION) { apfl_raise_const_error(ctx, "apfl_cfunc_defer must be called from within a cfunc"); } if (!apfl_resizable_append( ctx->gc.allocator, sizeof(struct cfunc_deferred), (void **)&cse->cfunc.deferred_list, &cse->cfunc.deferred_len, &cse->cfunc.deferred_cap, &(struct cfunc_deferred) { .cb = cb, .opaque = opaque, }, 1 )) { apfl_raise_alloc_error(ctx); } } void apfl_cfunc_run_deferred(apfl_ctx ctx, struct call_stack_entry *cse) { assert(cse != NULL); assert(cse->type == APFL_CSE_CFUNCTION); for (size_t i = cse->cfunc.deferred_len; i-- > 0; ) { struct cfunc_deferred *deferred = &cse->cfunc.deferred_list[i]; deferred->cb(ctx, deferred->opaque); } }