From a4f7f0f2fffba5998104513dd0c967199037a8c8 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Mon, 11 Jul 2022 21:41:05 +0200 Subject: [PATCH] Implement functions and function calls We can now define and call functions. Lexical closure scopes are also working :). It's limited to simple functions or complex functions with a single argument list of only variable names for now. --- src/apfl.h | 24 ++ src/bytecode.c | 24 ++ src/bytecode.h | 11 +- src/compile.c | 161 +++++++++++- src/context.c | 549 ++++++++++++++++++++++++++++++++++++----- src/context.h | 74 +++++- src/eval.c | 652 ++++++++++++++++++++++++++++++++++++++++--------- src/gc.c | 20 ++ src/gc.h | 4 + src/messages.c | 4 + src/scope.c | 40 +++ src/scope.h | 4 + src/value.c | 126 ++++++++++ src/value.h | 27 +- 14 files changed, 1522 insertions(+), 198 deletions(-) diff --git a/src/apfl.h b/src/apfl.h index e6e88d8..20de5f4 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -591,9 +591,11 @@ enum apfl_value_type { APFL_VALUE_LIST, APFL_VALUE_DICT, APFL_VALUE_FUNC, + APFL_VALUE_USERDATA, }; typedef int apfl_stackidx; +typedef size_t apfl_slotidx; enum apfl_result { APFL_RESULT_OK, // Evaluation succeeded, value is on the stack @@ -621,6 +623,8 @@ enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner); bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner); void apfl_iterative_runner_destroy(apfl_iterative_runner); +typedef void (*apfl_cfunc)(apfl_ctx); + // Get the type of a value on the stack enum apfl_value_type apfl_get_type(apfl_ctx, apfl_stackidx); @@ -646,6 +650,22 @@ void apfl_dict_create(apfl_ctx); void apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v); // Get a value from a container (list or dict) and push it on the stack. container and k will be dropped. void apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k); +// Get a value from a list and push it on the stack. The list will stay on the stack. +void apfl_get_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in_list); + +// Push a C function onto the stack with nslots slots (initialized to nil) +void apfl_push_cfunc(apfl_ctx, apfl_cfunc, size_t nslots); + +void apfl_cfunc_getslot(apfl_ctx, apfl_stackidx cfunc, apfl_slotidx); +void apfl_cfunc_self_getslot(apfl_ctx, apfl_slotidx); +void apfl_cfunc_setslot(apfl_ctx, apfl_stackidx cfunc, apfl_slotidx, apfl_stackidx value); +void apfl_cfunc_self_setslot(apfl_ctx, apfl_slotidx, apfl_stackidx value); + +void apfl_push_userdata(apfl_ctx, void *); +void *apfl_get_userdata(apfl_ctx, apfl_stackidx); + +void apfl_call(apfl_ctx, apfl_stackidx func, apfl_stackidx args); +enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack); bool apfl_debug_print_val(apfl_ctx, apfl_stackidx, struct apfl_format_writer); @@ -662,6 +682,10 @@ struct apfl_messages { const char *value_is_not_a_container; const char *wrong_key_type; const char *variable_doesnt_exist; + const char *not_a_c_function; + const char *invalid_slotidx; + const char *not_a_function; + const char *wrong_type; }; extern const struct apfl_messages apfl_messages; diff --git a/src/bytecode.c b/src/bytecode.c index 1bf6422..6446655 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -52,19 +52,29 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi case INSN_DICT_APPEND_KVPAIR: case INSN_GET_MEMBER: case INSN_NEXT_LINE: + case INSN_DROP: + case INSN_CALL: break; case INSN_NUMBER: case INSN_LIST: case INSN_SET_LINE: + case INSN_GET_BY_INDEX_KEEP: i++; break; case INSN_STRING: case INSN_VAR_GET: case INSN_VAR_SET: + case INSN_VAR_SET_LOCAL: case INSN_VAR_NEW: + case INSN_VAR_NEW_LOCAL: + case INSN_MOVE_TO_LOCAL_VAR: GET_ARGUMENT(ilist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); break; + case INSN_FUNC: + GET_ARGUMENT(ilist, i, arg); + cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS)); + break; } } } @@ -99,12 +109,26 @@ apfl_instruction_to_string(enum instruction insn) return "INSN_VAR_GET"; case INSN_VAR_SET: return "INSN_VAR_SET"; + case INSN_VAR_SET_LOCAL: + return "INSN_VAR_SET_LOCAL"; case INSN_VAR_NEW: return "INSN_VAR_NEW"; + case INSN_VAR_NEW_LOCAL: + return "INSN_VAR_NEW_LOCAL"; + case INSN_MOVE_TO_LOCAL_VAR: + return "INSN_MOVE_TO_LOCAL_VAR"; case INSN_NEXT_LINE: return "INSN_NEXT_LINE"; case INSN_SET_LINE: return "INSN_SET_LINE"; + case INSN_GET_BY_INDEX_KEEP: + return "INSN_GET_BY_INDEX_KEEP"; + case INSN_DROP: + return "INSN_DROP"; + case INSN_CALL: + return "INSN_CALL"; + case INSN_FUNC: + return "INSN_FUNC"; } return "??"; diff --git a/src/bytecode.h b/src/bytecode.h index bdb01ed..57e7a86 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -21,11 +21,18 @@ enum instruction { INSN_DICT, // ( -- dict ) INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' ) INSN_GET_MEMBER, // ( list/dict key -- value ) + INSN_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index INSN_VAR_GET, // ( -- value ), arg: string - INSN_VAR_SET, // ( value -- ), arg: string + INSN_VAR_SET, // ( value -- value ), arg: string + INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string INSN_VAR_NEW, // ( -- ), arg: string + INSN_VAR_NEW_LOCAL, // ( -- ), arg: string + INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string INSN_NEXT_LINE, // ( -- ) INSN_SET_LINE, // ( -- ), arg: count (new line number) + INSN_DROP, // ( value -- ) + INSN_CALL, // ( func list -- value ) + INSN_FUNC, // ( -- func ), arg: body }; union instruction_or_arg { @@ -33,6 +40,8 @@ union instruction_or_arg { struct apfl_string *string; apfl_number number; size_t count; + size_t index; + struct instruction_list *body; }; struct instruction_list { diff --git a/src/compile.c b/src/compile.c index 7f6c607..3605bd1 100644 --- a/src/compile.c +++ b/src/compile.c @@ -9,7 +9,7 @@ #include "resizable.h" #include "strings.h" -#define DEBUG_COMPILING 1 +#define DEBUG_COMPILING 0 #if DEBUG_COMPILING # define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__); @@ -91,6 +91,18 @@ ilist_put_string(struct instruction_list *ilist, struct apfl_string *string) ILIST_PUT(ilist, string, string, "put_string: " APFL_STR_FMT, APFL_STR_FMT_ARGS(*string)) } +static void +ilist_put_body(struct instruction_list *ilist, struct instruction_list *body) +{ + ILIST_PUT(ilist, body, body, "put_body: %p", (void *)body) +} + +static void +ilist_put_index(struct instruction_list *ilist, size_t index) +{ + ILIST_PUT(ilist, index, index, "put_index: %d", (int)index) +} + static bool string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in) { @@ -220,25 +232,44 @@ compile_simple_assignment( struct compiler *compiler, struct apfl_string *var, struct apfl_expr *rhs, - struct instruction_list *ilist + struct instruction_list *ilist, + enum instruction new_insn, + enum instruction set_insn ) { TRY(ilist_ensure_cap(compiler, ilist, 2)); struct apfl_string *str; TRY(string_move_into_new(compiler, &str, var)); - ilist_put_insn(ilist, INSN_VAR_NEW); + ilist_put_insn(ilist, new_insn); ilist_put_string(ilist, str); TRY(compile_expr(compiler, rhs, ilist)); TRY(ilist_ensure_cap(compiler, ilist, 2)); - ilist_put_insn(ilist, INSN_VAR_SET); + ilist_put_insn(ilist, set_insn); ilist_put_string(ilist, str); return true; } +static struct instruction_list * +tmp_ilist(struct compiler *compiler, int line) +{ + struct instruction_list *ilist; + if ( + (ilist = apfl_instructions_new(compiler->gc, line)) == NULL + || !apfl_gc_tmproot_add( + compiler->gc, + GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS) + ) + ) { + return NULL; + } + + return ilist; +} + static bool compile_assignment( struct compiler *compiler, @@ -253,7 +284,9 @@ compile_assignment( compiler, &assignment->lhs.var_or_member.var, assignment->rhs, - ilist + ilist, + assignment->local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW, + assignment->local ? INSN_VAR_SET_LOCAL : INSN_VAR_SET ); } @@ -262,6 +295,119 @@ compile_assignment( return false; } +static bool +compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct instruction_list *ilist) +{ + TRY(compile_expr(compiler, call->callee, ilist)); + TRY(compile_list(compiler, &call->arguments, ilist)); + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_CALL); + return true; +} + +static bool +compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist) +{ + for (size_t i = 0; i < body->len; i++) { + TRY(compile_expr(compiler, &body->items[i], ilist)); + } + + return true; +} + +static bool +compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line) +{ + struct instruction_list *body_ilist = NULL; + MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line))); + + // Drop the argument list, we ignore it in simple functions + TRY(ilist_ensure_cap(compiler, body_ilist, 1)); + ilist_put_insn(body_ilist, INSN_DROP); + + TRY(compile_body(compiler, func, body_ilist)); + + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_FUNC); + ilist_put_body(ilist, body_ilist); + + return true; +} + +static bool +compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line) +{ + size_t tmproots = apfl_gc_tmproots_begin(compiler->gc); + bool ok = compile_simple_func_inner(compiler, func, ilist, line); + apfl_gc_tmproots_restore(compiler->gc, tmproots); + return ok; +} + +static bool +compile_complex_func_inner(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line) +{ + if (func->len != 1) { + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); + return false; + } + + struct apfl_expr_subfunc *subfunc = &func->subfuncs[0]; + + struct instruction_list *body_ilist = NULL; + MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line))); + + for (size_t i = 0; i < subfunc->params.len; i++) { + struct apfl_expr_params_item *param = &subfunc->params.params[i]; + if (param->expand) { + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); + return false; + } + + switch (param->param.type) { + case APFL_EXPR_PARAM_VAR: + { + TRY(ilist_ensure_cap(compiler, body_ilist, 4)); + + struct apfl_string *str; + TRY(string_move_into_new(compiler, &str, ¶m->param.var)); + + ilist_put_insn(body_ilist, INSN_GET_BY_INDEX_KEEP); + ilist_put_index(body_ilist, i); + ilist_put_insn(body_ilist, INSN_MOVE_TO_LOCAL_VAR); + ilist_put_string(body_ilist, str); + break; + } + case APFL_EXPR_PARAM_CONSTANT: + case APFL_EXPR_PARAM_PREDICATE: + case APFL_EXPR_PARAM_LIST: + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); + return false; + case APFL_EXPR_PARAM_BLANK: + break; + } + } + + TRY(ilist_ensure_cap(compiler, body_ilist, 1)); + ilist_put_insn(body_ilist, INSN_DROP); // Drop the argument list + + TRY(compile_body(compiler, &subfunc->body, body_ilist)); + + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_FUNC); + ilist_put_body(ilist, body_ilist); + + return true; +} + +static bool +compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line) +{ + size_t tmproots = apfl_gc_tmproots_begin(compiler->gc); + bool ok = compile_complex_func_inner(compiler, func, ilist, line); + apfl_gc_tmproots_restore(compiler->gc, tmproots); + return ok; +} + static bool compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist) { @@ -281,10 +427,11 @@ compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instructi switch (expr->type) { case APFL_EXPR_CALL: + return compile_call(compiler, &expr->call, ilist); case APFL_EXPR_SIMPLE_FUNC: + return compile_simple_func(compiler, &expr->simple_func, ilist, new_line); case APFL_EXPR_COMPLEX_FUNC: - compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); - return false; + return compile_complex_func(compiler, &expr->complex_func, ilist, new_line); case APFL_EXPR_CONSTANT: return compile_constant(compiler, &expr->constant, ilist); case APFL_EXPR_BLANK: diff --git a/src/context.c b/src/context.c index 8612e53..6b09325 100644 --- a/src/context.c +++ b/src/context.c @@ -13,6 +13,7 @@ #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); APFL_NORETURN static void panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result) @@ -25,14 +26,12 @@ panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result) abort(); } -#define RESULT_OFF_FOR_LONGJMP 1 - enum apfl_result -apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *with_error_on_stack) +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 stack_len = ctx->stack.len; + size_t callstack_len = ctx->call_stack.len; size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); struct error_handler handler; @@ -41,7 +40,7 @@ apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *wit enum apfl_result result = APFL_RESULT_OK; int rv = setjmp(handler.jump); if (rv == 0) { - cb(ctx, opaque); + apfl_call(ctx, func, args); } else { result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP); assert(result != APFL_RESULT_OK); @@ -55,8 +54,23 @@ apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *wit } apfl_gc_tmproots_restore(&ctx->gc, tmproots); - assert(stack_len <= ctx->stack.cap); - ctx->stack.len = stack_len; + 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)) { @@ -91,7 +105,7 @@ raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type) APFL_NORETURN void apfl_raise_error_with_type(apfl_ctx ctx, apfl_stackidx idx, enum apfl_result type) { - bool ok = apfl_stack_move_to_top(ctx, idx); + bool ok = current_stack_move_to_top(ctx, idx); raise_error(ctx, ok, type); } @@ -145,8 +159,8 @@ apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque) ctx->panic_callback_data = opaque; } -static struct stack -stack_new() +struct stack +apfl_stack_new(void) { return (struct stack) { .items = NULL, @@ -155,6 +169,15 @@ stack_new() }; } +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) { @@ -166,34 +189,48 @@ apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value) 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 **)&ctx->stack.items, - &ctx->stack.len, - &ctx->stack.cap, + (void **)&stack->items, + &stack->len, + &stack->cap, &value, 1 ); } -bool -apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index) +static bool +stack_check_index(struct stack *stack, apfl_stackidx *index) { if (*index < 0) { - if ((size_t)-*index > ctx->stack.len) { + if ((size_t)-*index > stack->len) { return false; } - *index = ctx->stack.len + *index; - } else if ((size_t)*index >= ctx->stack.len) { + *index = stack->len + *index; + } else if ((size_t)*index >= stack->len) { return false; } - assert(0 <= *index && (size_t)*index < ctx->stack.len); + 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) { @@ -202,10 +239,13 @@ cmp_stackidx(const void *_a, const void *_b) return *a - *b; } -bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices) +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 (!apfl_stack_check_index(ctx, &indices[i])) { + if (!stack_check_index(stack, &indices[i])) { return false; } } @@ -216,8 +256,8 @@ bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices) // Will not fail, as we've already checked the indices assert(apfl_resizable_cut_without_resize( sizeof(struct apfl_value), - (void **)&ctx->stack.items, - &ctx->stack.len, + (void **)&stack->items, + &stack->len, indices[i], 1 )); @@ -231,18 +271,20 @@ bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices) bool apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) { - if (!apfl_stack_check_index(ctx, &index)) { + struct stack *stack = current_value_stack(ctx); + + if (!stack_check_index(stack, &index)) { return false; } - *value = ctx->stack.items[index]; + *value = stack->items[index]; assert(apfl_resizable_splice( ctx->gc.allocator, sizeof(struct apfl_value), - (void **)&ctx->stack.items, - &ctx->stack.len, - &ctx->stack.cap, + (void **)&stack->items, + &stack->len, + &stack->cap, index, 1, NULL, @@ -252,32 +294,60 @@ apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) 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) { - if (!apfl_stack_check_index(ctx, &index)) { + struct stack *stack = current_value_stack(ctx); + + if (!stack_check_index(stack, &index)) { return NULL; } - return &ctx->stack.items[index]; + return &stack->items[index]; } static bool -stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index) +stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index) { - if (!apfl_stack_check_index(ctx, index)) { + if (!stack_check_index(stack, index)) { return false; } - *value = ctx->stack.items[*index]; + *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 stack_get_and_adjust_index(ctx, value, &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 * @@ -314,25 +384,27 @@ apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index) return apfl_stack_pop(ctx, &value, index); } -bool -apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index) +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(ctx, &val, &index)) { + 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(ctx->stack.len >= absindex+1); + assert(stack->len >= absindex+1); memmove( - &ctx->stack.items[absindex + 1], - &ctx->stack.items[absindex], - ctx->stack.len - absindex - 1 + &stack->items[absindex + 1], + &stack->items[absindex], + stack->len - absindex - 1 ); - ctx->stack.items[ctx->stack.len-1] = val; + stack->items[stack->len-1] = val; return true; } @@ -340,30 +412,113 @@ apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index) void apfl_stack_clear(apfl_ctx ctx) { - ctx->stack.len = 0; + struct stack *stack = current_value_stack(ctx); + stack->len = 0; } static void -stack_traverse(struct stack *stack, gc_visitor visitor, void *opaque) +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]); + 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); + break; + case CSE_CFUNCTION: + visitor( + opaque, + GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC) + ); + break; + } + +} + static void get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque) { apfl_ctx ctx = own_opaque; - if (ctx->scope != NULL) { - visitor(visitor_opaque, GC_OBJECT_FROM(ctx->scope, GC_TYPE_SCOPE)); + if (ctx->globals != NULL) { + visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE)); } - stack_traverse(&ctx->stack, visitor, visitor_opaque); + 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); +} + +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, + }; } apfl_ctx @@ -376,16 +531,19 @@ apfl_ctx_new(struct apfl_allocator base_allocator) apfl_gc_init(&ctx->gc, base_allocator, get_roots, ctx); - if ((ctx->scope = apfl_scope_new(&ctx->gc)) == NULL) { + ctx->toplevel_stack = apfl_stack_new(); + ctx->call_stack = call_stack_new(); + + if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) { goto error; } - ctx->stack = stack_new(); - ctx->error_handler = NULL; ctx->panic_callback = NULL; ctx->panic_callback_data = NULL; + ctx->iterative_runners = iterative_runners_list_new(); + return ctx; error: @@ -400,17 +558,90 @@ apfl_ctx_destroy(apfl_ctx ctx) 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_LIST(ctx->gc.allocator, ctx->stack.items, ctx->stack.cap); - ctx->stack = stack_new(); - 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; +} + +bool +apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner) +{ + 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 + ); +} + +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) { \ @@ -519,10 +750,7 @@ apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_ind apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list); } - struct apfl_value value; - if (!apfl_stack_get(ctx, &value, value_index)) { - apfl_raise_invalid_stackidx(ctx); - } + struct apfl_value value = apfl_stack_must_get(ctx, value_index); if (!apfl_list_splice( &ctx->gc, @@ -532,6 +760,7 @@ apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_ind &value, 1 )) { + assert(apfl_stack_drop(ctx, value_index)); apfl_raise_alloc_error(ctx); } @@ -550,10 +779,7 @@ apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_i apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list); } - struct apfl_value src_val; - if (!apfl_stack_get(ctx, &src_val, src_index)) { - apfl_raise_invalid_stackidx(ctx); - } + 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)); @@ -626,8 +852,8 @@ apfl_get_member( struct apfl_value container; struct apfl_value k; if ( - !stack_get_and_adjust_index(ctx, &container, &container_index) - || !stack_get_and_adjust_index(ctx, &k, &k_index) + !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); } @@ -659,3 +885,196 @@ apfl_get_member( 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; +} + +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; +} diff --git a/src/context.h b/src/context.h index a9ddb7b..e85cce2 100644 --- a/src/context.h +++ b/src/context.h @@ -14,27 +14,76 @@ extern "C" { #include "value.h" #include "scope.h" +#define RESULT_OFF_FOR_LONGJMP 1 + struct stack { struct apfl_value *items; size_t len; size_t cap; }; +enum call_stack_entry_type { + CSE_FUNCTION, + CSE_CFUNCTION, +}; + +struct func_call_stack_entry { + size_t pc; + struct instruction_list *instructions; + + // Both scope and closure_scope can be null (scope will be created lazily) + struct scope *scope; + struct scope *closure_scope; + + int execution_line; +}; + +struct cfunc_call_stack_entry { + struct cfunction *func; +}; + +struct call_stack_entry { + enum call_stack_entry_type type; + + struct stack stack; + + union { + struct func_call_stack_entry func; + struct cfunc_call_stack_entry cfunc; + }; +}; + +struct call_stack { + struct call_stack_entry *items; + size_t len; + size_t cap; +}; + struct error_handler { jmp_buf jump; bool with_error_on_stack; }; +struct iterative_runners_list { + apfl_iterative_runner *items; + size_t len; + size_t cap; +}; + struct apfl_ctx_data { struct gc gc; apfl_panic_callback panic_callback; void *panic_callback_data; - struct scope *scope; - struct stack stack; + + struct stack toplevel_stack; + + struct scope *globals; + struct call_stack call_stack; + struct error_handler *error_handler; - int execution_line; + struct iterative_runners_list iterative_runners; }; APFL_NORETURN void apfl_raise_error_with_type(apfl_ctx, apfl_stackidx, enum apfl_result type); @@ -43,19 +92,32 @@ APFL_NORETURN void apfl_raise_alloc_error(apfl_ctx); APFL_NORETURN void apfl_raise_invalid_stackidx(apfl_ctx); APFL_NORETURN void apfl_raise_error_object(apfl_ctx, struct apfl_error); +void apfl_call_stack_entry_deinit(struct apfl_allocator, struct call_stack_entry *); + +struct stack apfl_stack_new(void); + bool apfl_stack_push(apfl_ctx, struct apfl_value); void apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value); bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *); +bool apfl_stack_has_index(apfl_ctx, apfl_stackidx); bool apfl_stack_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx); +struct apfl_value apfl_stack_must_pop(apfl_ctx, apfl_stackidx); bool apfl_stack_get(apfl_ctx, struct apfl_value *value, apfl_stackidx); +struct apfl_value apfl_stack_must_get(apfl_ctx, apfl_stackidx); bool apfl_stack_drop(apfl_ctx, apfl_stackidx); -bool apfl_stack_move_to_top(apfl_ctx, apfl_stackidx); +bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices); void apfl_stack_clear(apfl_ctx); struct apfl_value *apfl_stack_push_placeholder(apfl_ctx); bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string); -typedef void (*apfl_protected_callback)(apfl_ctx, void *); -enum apfl_result apfl_protected(apfl_ctx, apfl_protected_callback, void *, bool *with_error_on_stack); +struct call_stack_entry *apfl_call_stack_cur_entry(apfl_ctx); + +struct scope *apfl_closure_scope_for_func(apfl_ctx); + +bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner); +void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner); + +void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, void *); #ifdef __cplusplus } diff --git a/src/eval.c b/src/eval.c index 6b0ae91..fe94a52 100644 --- a/src/eval.c +++ b/src/eval.c @@ -8,9 +8,12 @@ #include "context.h" #include "format.h" #include "hashmap.h" +#include "resizable.h" #include "strings.h" #include "value.h" +static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse); + static void stack_must_drop(apfl_ctx ctx, apfl_stackidx index) { @@ -36,28 +39,135 @@ must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union } } -static void -variable_get(apfl_ctx ctx, struct apfl_string *name) +static struct func_call_stack_entry * +get_current_func_cse(apfl_ctx ctx) { - struct apfl_value value; - if (!apfl_scope_get(ctx->scope, name, &value)) { - // TODO: Include variable name in error - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.variable_doesnt_exist); + struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); + if (cse == NULL) { + return NULL; } - apfl_stack_must_push(ctx, value); + 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_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack) +variable_get(apfl_ctx ctx, struct apfl_string *name) { - struct apfl_value value; - if (!apfl_stack_get(ctx, &value, -1)) { - apfl_raise_invalid_stackidx(ctx); + if (try_variable_get_for_scope_type(ctx, name, SCOPE_LOCAL)) { + return; } - if (!apfl_scope_set(&ctx->gc, ctx->scope, name, value)) { - apfl_raise_alloc_error(ctx); + 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(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local) +{ + struct apfl_value value = apfl_stack_must_get(ctx, -1); + + 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); + } + } + 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 @@ -68,97 +178,363 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack) } } -static void -variable_new(apfl_ctx ctx, struct apfl_string *name) +static bool +variable_exists_by_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type) { - if (!apfl_scope_create_var(&ctx->gc, ctx->scope, name)) { + 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 -evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist) +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 +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 }; + } + + 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 + ) + ); + + 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 only be called with a CSE_FUNCTION 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); + assert(cse->type == CSE_FUNCTION); + + evaluate(ctx, &cse->func); + } +} + +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, + }, + }); + + 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 +evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) { union instruction_or_arg arg; - assert(*i < ilist->len); - switch (ilist->instructions[(*i)++].instruction) { - case INSN_NIL: - apfl_push_nil(ctx); - return; - case INSN_TRUE: - apfl_push_bool(ctx, true); - return; - case INSN_FALSE: - apfl_push_bool(ctx, false); - return; - case INSN_NUMBER: - must_get_argument(ctx, i, ilist, &arg); - apfl_push_number(ctx, arg.number); - return; - case INSN_STRING: - must_get_argument(ctx, i, ilist, &arg); - apfl_stack_must_push(ctx, (struct apfl_value) { - .type = VALUE_STRING, - .string = arg.string, - }); - return; - case INSN_LIST: - must_get_argument(ctx, i, ilist, &arg); - apfl_list_create(ctx, arg.count); - return; - case INSN_LIST_APPEND: - apfl_list_append(ctx, -2, -1); - return; - case INSN_LIST_EXPAND_INTO: - apfl_list_append_list(ctx, -2, -1); - return; - case INSN_DICT: - apfl_dict_create(ctx); - return; - case INSN_DICT_APPEND_KVPAIR: - apfl_dict_set(ctx, -3, -2, -1); - return; - case INSN_GET_MEMBER: - apfl_get_member(ctx, -2, -1); - return; - case INSN_VAR_NEW: - must_get_argument(ctx, i, ilist, &arg); - variable_new(ctx, arg.string); - return; - case INSN_VAR_GET: - must_get_argument(ctx, i, ilist, &arg); - variable_get(ctx, arg.string); - return; - case INSN_VAR_SET: - must_get_argument(ctx, i, ilist, &arg); - variable_set(ctx, arg.string, true); - return; - case INSN_NEXT_LINE: - ctx->execution_line++; - return; - case INSN_SET_LINE: - must_get_argument(ctx, i, ilist, &arg); - ctx->execution_line = arg.count; - return; + 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; + } + + assert(false); + + continue_loop:; } - assert(false); + return_from_function(ctx); } -static void -evaluate_list(apfl_ctx ctx, struct instruction_list *ilist) -{ - ctx->execution_line = ilist->line; - size_t i = 0; - while (i < ilist->len) { - evaluate(ctx, &i, ilist); - } -} +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 -eval_expr_inner(apfl_ctx ctx, struct apfl_expr expr) +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); @@ -173,14 +549,26 @@ eval_expr_inner(apfl_ctx ctx, struct apfl_expr expr) apfl_raise_error_object(ctx, error); } - evaluate_list(ctx, ilist); + 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, + }, + }); + evaluate_until_call_stack_return(ctx); } static void -eval_expr(apfl_ctx ctx, struct apfl_expr expr) +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); - eval_expr_inner(ctx, expr); + iterative_runner_eval_expr_inner(runner, expr); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } @@ -198,34 +586,31 @@ apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, struct apfl_format_write return apfl_value_print(value, w); } -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; -}; - apfl_iterative_runner apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader) { - apfl_iterative_runner runner = ALLOC_OBJ(ctx->gc.allocator, struct apfl_iterative_runner_data); + 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; } - apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader); + tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader); if (tokenizer == NULL) { - FREE_OBJ(ctx->gc.allocator, runner); - return NULL; + goto error; } - apfl_parser_ptr parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer)); + parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer)); if (parser == NULL) { - FREE_OBJ(ctx->gc.allocator, runner); - apfl_tokenizer_destroy(tokenizer); - return NULL; + goto error; + } + + struct scope *scope = apfl_scope_new(&ctx->gc); + if (scope == NULL) { + goto error; } *runner = (struct apfl_iterative_runner_data) { @@ -234,9 +619,21 @@ apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader) .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 @@ -265,13 +662,15 @@ handle_parse_error(apfl_ctx ctx, struct apfl_error error) } static void -iterative_runner_next_protected(apfl_ctx ctx, void *opaque) +iterative_runner_next_protected(apfl_ctx ctx) { - apfl_iterative_runner runner = opaque; + 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: - eval_expr(ctx, apfl_parser_get_expr(runner->parser)); + 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)); @@ -293,13 +692,15 @@ apfl_iterative_runner_next(apfl_iterative_runner runner) apfl_stack_clear(runner->ctx); - runner->with_error_on_stack = false; - runner->result = apfl_protected( - runner->ctx, - iterative_runner_next_protected, - runner, - &runner->with_error_on_stack - ); + // 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; } @@ -325,5 +726,20 @@ apfl_iterative_runner_destroy(apfl_iterative_runner runner) 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)); + } +} diff --git a/src/gc.c b/src/gc.c index 46ec854..99e28aa 100644 --- a/src/gc.c +++ b/src/gc.c @@ -23,6 +23,8 @@ struct gc_object { struct instruction_list instructions; struct scope scope; struct stack stack; + struct function function; + struct cfunction cfunction; }; enum gc_type type; enum gc_status status; @@ -157,6 +159,8 @@ IMPL_NEW(struct apfl_value, apfl_gc_new_var, GC_T IMPL_NEW(struct apfl_string, apfl_gc_new_string, GC_TYPE_STRING, string ) IMPL_NEW(struct instruction_list, apfl_gc_new_instructions, GC_TYPE_INSTRUCTIONS, instructions ) IMPL_NEW(struct scope, apfl_gc_new_scope, GC_TYPE_SCOPE, scope ) +IMPL_NEW(struct function, apfl_gc_new_func, GC_TYPE_FUNC, function ) +IMPL_NEW(struct cfunction, apfl_gc_new_cfunc, GC_TYPE_CFUNC, cfunction ) size_t apfl_gc_tmproots_begin(struct gc *gc) @@ -250,6 +254,12 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque) case GC_TYPE_INSTRUCTIONS: apfl_gc_instructions_traverse(&object->instructions, cb, opaque); return; + case GC_TYPE_FUNC: + apfl_gc_func_traverse(&object->function, cb, opaque); + return; + case GC_TYPE_CFUNC: + apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque); + return; } assert(false); @@ -312,6 +322,12 @@ deinit_object(struct gc *gc, struct gc_object *object) case GC_TYPE_SCOPE: apfl_scope_deinit(gc->allocator, &object->scope); return; + case GC_TYPE_FUNC: + apfl_function_deinit(gc->allocator, &object->function); + return; + case GC_TYPE_CFUNC: + apfl_cfunction_deinit(gc->allocator, &object->cfunction); + return; } assert(false); @@ -405,6 +421,10 @@ type_to_string(enum gc_type type) return "instructions"; case GC_TYPE_SCOPE: return "scope"; + case GC_TYPE_FUNC: + return "func"; + case GC_TYPE_CFUNC: + return "cfunc"; } assert(false); diff --git a/src/gc.h b/src/gc.h index 34f2f3f..af1d90c 100644 --- a/src/gc.h +++ b/src/gc.h @@ -27,6 +27,8 @@ enum gc_type { GC_TYPE_STRING, GC_TYPE_INSTRUCTIONS, GC_TYPE_SCOPE, + GC_TYPE_FUNC, + GC_TYPE_CFUNC, }; struct gc_tmproots { @@ -76,6 +78,8 @@ struct apfl_value* apfl_gc_new_var(struct gc *); struct apfl_string* apfl_gc_new_string(struct gc *); struct instruction_list* apfl_gc_new_instructions(struct gc *); struct scope* apfl_gc_new_scope(struct gc *); +struct function* apfl_gc_new_func(struct gc *); +struct cfunction* apfl_gc_new_cfunc(struct gc *); #ifdef __cplusplus } diff --git a/src/messages.c b/src/messages.c index b49acaa..6678aef 100644 --- a/src/messages.c +++ b/src/messages.c @@ -13,4 +13,8 @@ const struct apfl_messages apfl_messages = { .value_is_not_a_container = "Value is not a container (list or dictionary)", .wrong_key_type = "Wrong key type", .variable_doesnt_exist = "Variable does not exist", + .not_a_c_function = "Not a C function", + .invalid_slotidx = "Invalid slot index", + .not_a_function = "Not a function", + .wrong_type = "Wrong type", }; diff --git a/src/scope.c b/src/scope.c index de24cd8..002424f 100644 --- a/src/scope.c +++ b/src/scope.c @@ -101,6 +101,12 @@ apfl_scope_get(struct scope *scope, struct apfl_string *name, struct apfl_value return true; } +bool +apfl_scope_has(struct scope *scope, struct apfl_string *name) +{ + return apfl_hashmap_isset(&scope->map, &name); +} + static variable get_or_create_variable(struct gc *gc, struct scope *scope, struct apfl_string *name) { @@ -137,8 +143,42 @@ apfl_scope_set(struct gc *gc, struct scope *scope, struct apfl_string *name, str return true; } +bool +apfl_scope_update_existing(struct scope *scope, struct apfl_string *name, struct apfl_value value) +{ + variable var; + if (!apfl_hashmap_get(&scope->map, &name, &var)) { + return false; + } + + *var = value; + return true; +} + bool apfl_scope_create_var(struct gc *gc, struct scope *scope, struct apfl_string *name) { return get_or_create_variable(gc, scope, name) != NULL; } + +bool +apfl_scope_merge_into(struct scope *self, struct scope *other) +{ + if (self == NULL || other == NULL) { + return NULL; + } + + HASHMAP_EACH(&other->map, cur) { + struct apfl_string **k = apfl_hashmap_cursor_peek_key(cur); + assert(k != NULL); + + variable *v = apfl_hashmap_cursor_peek_value(cur); + assert(v != NULL); + + if (!apfl_hashmap_set(&self->map, k, v)) { + return false; + } + } + + return true; +} diff --git a/src/scope.h b/src/scope.h index 9cadb2f..9b8eea8 100644 --- a/src/scope.h +++ b/src/scope.h @@ -17,8 +17,12 @@ struct scope *apfl_scope_new(struct gc *); void apfl_scope_deinit(struct apfl_allocator, struct scope *); bool apfl_scope_get(struct scope *, struct apfl_string *name, struct apfl_value *out); +bool apfl_scope_has(struct scope *, struct apfl_string *name); bool apfl_scope_set(struct gc *, struct scope *, struct apfl_string *name, struct apfl_value value); bool apfl_scope_create_var(struct gc *, struct scope *, struct apfl_string *name); +bool apfl_scope_update_existing(struct scope *, struct apfl_string *, struct apfl_value); + +bool apfl_scope_merge_into(struct scope *self, struct scope *other); void apfl_gc_var_traverse(struct apfl_value *, gc_visitor, void *); void apfl_gc_scope_traverse(struct scope *, gc_visitor, void *); diff --git a/src/value.c b/src/value.c index fbc47a4..0daba01 100644 --- a/src/value.c +++ b/src/value.c @@ -142,6 +142,15 @@ format(unsigned indent, struct apfl_format_writer w, struct apfl_value value, bo TRY(apfl_format_put_indent(w, indent)); TRY(apfl_format_put_string(w, "]")); return true; + case VALUE_FUNC: + TRY(apfl_format_put_string(w, "{ ... }")); + return true; + case VALUE_CFUNC: + TRY(apfl_format_put_string(w, "{ native code }")); + return true; + case VALUE_USERDATA: + TRY(apfl_format_put_string(w, "userdata")); + return true; } TRY(apfl_format_put_string(w, "Unknown value ? (")); @@ -150,6 +159,90 @@ format(unsigned indent, struct apfl_format_writer w, struct apfl_value value, bo return true; } +enum apfl_value_type +apfl_value_type_to_abstract_type(enum value_type type) +{ + switch (type) { + case VALUE_NIL: + return APFL_VALUE_NIL; + case VALUE_BOOLEAN: + return APFL_VALUE_BOOLEAN; + case VALUE_NUMBER: + return APFL_VALUE_NUMBER; + case VALUE_STRING: + case VALUE_CONST_STRING: + return APFL_VALUE_STRING; + case VALUE_LIST: + return APFL_VALUE_LIST; + case VALUE_DICT: + return APFL_VALUE_DICT; + case VALUE_FUNC: + case VALUE_CFUNC: + return APFL_VALUE_FUNC; + case VALUE_USERDATA: + return APFL_VALUE_USERDATA; + } + + assert(false); + return 0; +} + +struct function * +apfl_func_new(struct gc *gc, struct instruction_list *body, struct scope *scope) +{ + struct function *function = apfl_gc_new_func(gc); + if (function == NULL) { + return NULL; + } + + *function = (struct function) { + .body = body, + .scope = scope, + }; + + return function; +} + +void +apfl_function_deinit(struct apfl_allocator allocator, struct function *function) +{ + (void)allocator; + (void)function; +} + +struct cfunction * +apfl_cfunc_new(struct gc *gc, apfl_cfunc func, size_t nslots) +{ + struct apfl_value **slots = NULL; + if (nslots > 0) { + slots = ALLOC_LIST(gc->allocator, struct apfl_value *, nslots); + if (slots == NULL) { + return NULL; + } + } + + struct cfunction *cfunction = apfl_gc_new_cfunc(gc); + if (cfunction == NULL) { + FREE_LIST(gc->allocator, slots, nslots); + } + + for (size_t i = 0; i < nslots; i++) { + slots[i] = NULL; + } + + cfunction->func = func; + cfunction->slots = slots; + cfunction->slots_len = nslots; + + return cfunction; +} + +void +apfl_cfunction_deinit(struct apfl_allocator allocator, struct cfunction *cfunc) +{ + FREE_LIST(allocator, cfunc->slots, cfunc->slots_len); +} + struct apfl_value apfl_value_move(struct apfl_value *src) { @@ -233,6 +326,12 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b) return b.type == VALUE_LIST && list_eq(a.list, b.list); case VALUE_DICT: return b.type == VALUE_DICT && dict_eq(a.dict, b.dict); + case VALUE_FUNC: + return b.type == VALUE_FUNC && a.func == b.func; + case VALUE_CFUNC: + return b.type == VALUE_CFUNC && a.cfunc == b.cfunc; + case VALUE_USERDATA: + return b.type == VALUE_USERDATA && a.userdata == b.userdata; } assert(false); @@ -488,6 +587,12 @@ apfl_value_hash(const struct apfl_value value) // it's rather unusual to have dictionaries as keys, this is fine // for now, but should be improved nonetheless! return hash; + case VALUE_FUNC: + return apfl_hash_fnv1a_add(&value.func, sizeof(struct function *), hash); + case VALUE_CFUNC: + return apfl_hash_fnv1a_add(&value.cfunc, sizeof(struct cfunction *), hash); + case VALUE_USERDATA: + return apfl_hash_fnv1a_add(&value.userdata, sizeof(void *), hash); } assert(false); @@ -503,6 +608,7 @@ apfl_value_get_gc_object(struct apfl_value value) case VALUE_BOOLEAN: case VALUE_NUMBER: case VALUE_CONST_STRING: + case VALUE_USERDATA: return NULL; case VALUE_STRING: return GC_OBJECT_FROM(value.string, GC_TYPE_STRING); @@ -510,6 +616,10 @@ apfl_value_get_gc_object(struct apfl_value value) return GC_OBJECT_FROM(value.list, GC_TYPE_LIST); case VALUE_DICT: return GC_OBJECT_FROM(value.dict, GC_TYPE_DICT); + case VALUE_FUNC: + return GC_OBJECT_FROM(value.func, GC_TYPE_FUNC); + case VALUE_CFUNC: + return GC_OBJECT_FROM(value.cfunc, GC_TYPE_CFUNC); } assert(false); @@ -547,3 +657,19 @@ apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque) } } +void +apfl_gc_func_traverse(struct function* function, gc_visitor cb, void *opaque) +{ + cb(opaque, GC_OBJECT_FROM(function->body, GC_TYPE_INSTRUCTIONS)); + cb(opaque, GC_OBJECT_FROM(function->scope, GC_TYPE_SCOPE)); +} + +void +apfl_gc_cfunc_traverse(struct cfunction* cfunc, gc_visitor cb, void *opaque) +{ + for (size_t i = 0; i < cfunc->slots_len; i++) { + if (cfunc->slots[i] != NULL) { + cb(opaque, GC_OBJECT_FROM(cfunc->slots[i], GC_TYPE_VAR)); + } + } +} diff --git a/src/value.h b/src/value.h index adaca1b..b09423c 100644 --- a/src/value.h +++ b/src/value.h @@ -11,6 +11,7 @@ extern "C" { #include "apfl.h" +#include "bytecode.h" #include "gc.h" #include "hashmap.h" @@ -23,7 +24,9 @@ enum value_type { VALUE_CONST_STRING, VALUE_LIST, VALUE_DICT, - // TODO: functions/closures + VALUE_FUNC, + VALUE_CFUNC, + VALUE_USERDATA, }; struct list_header { @@ -38,6 +41,17 @@ struct dict_header { bool copy_on_write; }; +struct function { + struct instruction_list *body; + struct scope *scope; +}; + +struct cfunction { + apfl_cfunc func; + struct apfl_value **slots; + size_t slots_len; +}; + typedef struct dict_header *apfl_dict; struct apfl_value { @@ -49,6 +63,9 @@ struct apfl_value { struct apfl_string_view const_string; struct list_header *list; struct dict_header *dict; + struct function *func; + struct cfunction *cfunc; + void *userdata; }; }; @@ -98,10 +115,18 @@ bool apfl_dict_set_raw( ); void apfl_dict_deinit(struct dict_header *); +struct function *apfl_func_new(struct gc *, struct instruction_list *, struct scope *); +void apfl_function_deinit(struct apfl_allocator, struct function *); + +struct cfunction *apfl_cfunc_new(struct gc *, apfl_cfunc, size_t nslots); +void apfl_cfunction_deinit(struct apfl_allocator, struct cfunction *); + // Functions for garbage collection struct gc_object *apfl_value_get_gc_object(struct apfl_value); void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *); void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *); +void apfl_gc_func_traverse(struct function*, gc_visitor, void *); +void apfl_gc_cfunc_traverse(struct cfunction*, gc_visitor, void *); #ifdef __cplusplus }