diff --git a/src/Makefile.am b/src/Makefile.am index 1abb548..bd357e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,22 +4,31 @@ lib_LIBRARIES = libapfl.a libapfl_a_SOURCES = libapfl_a_SOURCES += alloc.c +libapfl_a_SOURCES += bytecode.c +libapfl_a_SOURCES += compile.c +libapfl_a_SOURCES += context.c libapfl_a_SOURCES += error.c libapfl_a_SOURCES += eval.c libapfl_a_SOURCES += expr.c +libapfl_a_SOURCES += gc.c libapfl_a_SOURCES += hashmap.c libapfl_a_SOURCES += internal.c +libapfl_a_SOURCES += parser.c libapfl_a_SOURCES += position.c libapfl_a_SOURCES += resizable.c +libapfl_a_SOURCES += source_readers.c libapfl_a_SOURCES += strings.c libapfl_a_SOURCES += token.c libapfl_a_SOURCES += tokenizer.c -libapfl_a_SOURCES += parser.c -libapfl_a_SOURCES += source_readers.c libapfl_a_SOURCES += value.c apfl_internal_headers = -apfl_internal_headers += hashmap.c +apfl_internal_headers += alloc.h +apfl_internal_headers += bytecode.h +apfl_internal_headers += compile.h +apfl_internal_headers += context.h +apfl_internal_headers += gc.h +apfl_internal_headers += hashmap.h apfl_internal_headers += internal.h apfl_internal_headers += resizable.h apfl_internal_headers += value.h diff --git a/src/apfl.h b/src/apfl.h index 7e8fb0b..befcab8 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -62,10 +62,6 @@ struct apfl_string { size_t cap; }; -struct apfl_refcounted_string_data; - -typedef struct apfl_refcounted_string_data *apfl_refcounted_string; - #define APFL_STR_FMT "%.*s" #define APFL_STR_FMT_ARGS(s) (int)(s).len,(s).bytes @@ -73,12 +69,10 @@ struct apfl_string_view apfl_string_view_from_view(struct apfl_string_view); struct apfl_string_view apfl_string_view_from_cstr(char *); struct apfl_string_view apfl_string_view_from_const_cstr(const char *); struct apfl_string_view apfl_string_view_from_string(struct apfl_string); -struct apfl_string_view apfl_string_view_from_refcounted_string(apfl_refcounted_string); #define apfl_string_view_from(s) _Generic((s), \ struct apfl_string: apfl_string_view_from_string, \ struct apfl_string_view: apfl_string_view_from_view, \ - apfl_refcounted_string: apfl_string_view_from_refcounted_string, \ char *: apfl_string_view_from_cstr, \ const char *: apfl_string_view_from_const_cstr \ )(s) @@ -114,25 +108,6 @@ struct apfl_string apfl_string_builder_move_string(struct apfl_string_builder *) #define apfl_string_builder_append_cstr(builder, cstr) (apfl_string_builder_append((builder), apfl_string_view_from_cstr((cstr)))) -apfl_refcounted_string apfl_string_copy_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string_view); - -apfl_refcounted_string apfl_string_move_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string *); - -/* Increases the reference of the refcounted string. - * Returns the same apfl_refcounted_string value. - */ -apfl_refcounted_string apfl_refcounted_string_incref(apfl_refcounted_string); - -/* Unrefs the refcounted string. It is no longer allowed to use the pointer - * after calling this function with it! - */ -void apfl_refcounted_string_unref(struct apfl_allocator allocator, apfl_refcounted_string ); - -/* Like apfl_refcounted_string_unref, but accepts a pointer to a refcounted_string. - * The pointed to value will be set to NULL. - */ -void apfl_refcounted_string_unref_ptr(struct apfl_allocator allocator, apfl_refcounted_string *); - // Tokens enum apfl_token_type { @@ -164,7 +139,7 @@ struct apfl_token { enum apfl_token_type type; struct apfl_position position; union { - apfl_refcounted_string text; + struct apfl_string text; apfl_number number; }; }; @@ -200,6 +175,7 @@ enum apfl_error_type { APFL_ERR_UNEXPECTED_CONSTANT_IN_MEMBER_ACCESS, APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS, APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS, + APFL_ERR_NOT_IMPLEMENTED, }; const char *apfl_error_type_name(enum apfl_error_type); @@ -282,7 +258,7 @@ struct apfl_expr_const { union { // variant nil is without data bool boolean; - apfl_refcounted_string string; + struct apfl_string string; apfl_number number; }; }; @@ -310,7 +286,7 @@ struct apfl_expr_param { enum apfl_expr_param_type type; union { - apfl_refcounted_string var; + struct apfl_string var; struct apfl_expr_const constant; struct apfl_expr_param_predicate predicate; struct apfl_expr_params list; @@ -350,7 +326,7 @@ enum apfl_expr_assignable_var_or_member_type { struct apfl_expr_assignable_var_or_member_dot { struct apfl_expr_assignable_var_or_member *lhs; - apfl_refcounted_string rhs; + struct apfl_string rhs; }; struct apfl_expr_assignable_var_or_member_at { struct apfl_expr_assignable_var_or_member *lhs; @@ -361,7 +337,7 @@ struct apfl_expr_assignable_var_or_member { enum apfl_expr_assignable_var_or_member_type type; union { - apfl_refcounted_string var; + struct apfl_string var; struct apfl_expr_assignable_var_or_member_dot dot; struct apfl_expr_assignable_var_or_member_at at; }; @@ -400,7 +376,7 @@ struct apfl_expr_assignment { struct apfl_expr_dot { struct apfl_expr *lhs; - apfl_refcounted_string rhs; + struct apfl_string rhs; }; struct apfl_expr_at { @@ -421,7 +397,7 @@ struct apfl_expr { struct apfl_expr_dot dot; struct apfl_expr_at at; struct apfl_expr_const constant; - apfl_refcounted_string var; + struct apfl_string var; // blank has no further data }; @@ -565,6 +541,16 @@ struct apfl_expr apfl_parser_get_expr(apfl_parser_ptr); struct apfl_ctx_data; typedef struct apfl_ctx_data *apfl_ctx; +enum apfl_value_type { + APFL_VALUE_NIL, + APFL_VALUE_BOOLEAN, + APFL_VALUE_NUMBER, + APFL_VALUE_STRING, + APFL_VALUE_LIST, + APFL_VALUE_DICT, + APFL_VALUE_FUNC, +}; + typedef int apfl_stackidx; enum apfl_result { @@ -583,6 +569,22 @@ void apfl_ctx_destroy(apfl_ctx); enum apfl_result apfl_eval(apfl_ctx, struct apfl_expr); +// Get the type of a value on the stack +enum apfl_value_type apfl_get_type(apfl_ctx, apfl_stackidx); + +// Create a new empty list on the stack +enum apfl_result apfl_list_create(apfl_ctx, size_t initial_capacity); +// Append a value to a list. The value will be dropped. +enum apfl_result apfl_list_append(apfl_ctx, apfl_stackidx list, apfl_stackidx value); +// Append a list to another list. The second list will be dropped. +enum apfl_result apfl_list_append_list(apfl_ctx, apfl_stackidx a, apfl_stackidx b); +// Create a new empty dict on the stack +enum apfl_result apfl_dict_create(apfl_ctx); +// Set a value in a dictionary. k and v will be dropped. +enum apfl_result 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. +enum apfl_result apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k); + void apfl_debug_print_val(apfl_ctx, apfl_stackidx, FILE *); #ifdef __cplusplus diff --git a/src/bytecode.c b/src/bytecode.c new file mode 100644 index 0000000..4f8487c --- /dev/null +++ b/src/bytecode.c @@ -0,0 +1,111 @@ +#include + +#include "apfl.h" + +#include "alloc.h" +#include "bytecode.h" +#include "gc.h" + +struct instruction_list * +apfl_instructions_new(struct gc *gc, int line) +{ + struct instruction_list *ilist = apfl_gc_new_instructions(gc); + if (ilist == NULL) { + return NULL; + } + *ilist = (struct instruction_list) { + .instructions = NULL, + .len = 0, + .cap = 0, + .line = line, + }; + return ilist; +} + +void +apfl_instructions_deinit(struct apfl_allocator allocator, struct instruction_list *ilist) +{ + FREE_LIST(allocator, ilist->instructions, ilist->cap); +} + +#define GET_ARGUMENT(ilist, i, arg) \ + do { \ + if (i >= ilist->len) { \ + return; \ + } \ + arg = ilist->instructions[++i]; \ + } while (0) + +void +apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, void *opaque) +{ + union instruction_or_arg arg; + + for (size_t i = 0; i < ilist->len; i++) { + switch (ilist->instructions[i].instruction) { + case INSN_NIL: + case INSN_TRUE: + case INSN_FALSE: + case INSN_LIST_APPEND: + case INSN_LIST_EXPAND_INTO: + case INSN_DICT: + case INSN_DICT_APPEND_KVPAIR: + case INSN_GET_MEMBER: + case INSN_NEXT_LINE: + break; + case INSN_NUMBER: + case INSN_LIST: + case INSN_SET_LINE: + i++; + break; + case INSN_STRING: + case INSN_VAR_GET: + case INSN_VAR_SET: + case INSN_VAR_NEW: + GET_ARGUMENT(ilist, i, arg); + cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); + return; + } + } +} + +const char * +apfl_instruction_to_string(enum instruction insn) +{ + switch (insn) { + case INSN_NIL: + return "INSN_NIL"; + case INSN_TRUE: + return "INSN_TRUE"; + case INSN_FALSE: + return "INSN_FALSE"; + case INSN_NUMBER: + return "INSN_NUMBER"; + case INSN_STRING: + return "INSN_STRING"; + case INSN_LIST: + return "INSN_LIST"; + case INSN_LIST_APPEND: + return "INSN_LIST_APPEND"; + case INSN_LIST_EXPAND_INTO: + return "INSN_LIST_EXPAND_INTO"; + case INSN_DICT: + return "INSN_DICT"; + case INSN_DICT_APPEND_KVPAIR: + return "INSN_DICT_APPEND_KVPAIR"; + case INSN_GET_MEMBER: + return "INSN_GET_MEMBER"; + case INSN_VAR_GET: + return "INSN_VAR_GET"; + case INSN_VAR_SET: + return "INSN_VAR_SET"; + case INSN_VAR_NEW: + return "INSN_VAR_NEW"; + case INSN_NEXT_LINE: + return "INSN_NEXT_LINE"; + case INSN_SET_LINE: + return "INSN_SET_LINE"; + } + + return "??"; +} diff --git a/src/bytecode.h b/src/bytecode.h new file mode 100644 index 0000000..bdb01ed --- /dev/null +++ b/src/bytecode.h @@ -0,0 +1,57 @@ +#ifndef APFL_BYTECODE_H +#define APFL_BYTECODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "apfl.h" + +#include "gc.h" + +enum instruction { + INSN_NIL, // ( -- nil) + INSN_TRUE, // ( -- true) + INSN_FALSE, // ( -- false) + INSN_NUMBER, // ( -- number), arg: number + INSN_STRING, // ( -- string), arg: string + INSN_LIST, // ( -- list), arg: count (preallocation hint) + INSN_LIST_APPEND, // ( list val -- list' ) + INSN_LIST_EXPAND_INTO, // ( list list -- list' ) + INSN_DICT, // ( -- dict ) + INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' ) + INSN_GET_MEMBER, // ( list/dict key -- value ) + INSN_VAR_GET, // ( -- value ), arg: string + INSN_VAR_SET, // ( value -- ), arg: string + INSN_VAR_NEW, // ( -- ), arg: string + INSN_NEXT_LINE, // ( -- ) + INSN_SET_LINE, // ( -- ), arg: count (new line number) +}; + +union instruction_or_arg { + enum instruction instruction; + struct apfl_string *string; + apfl_number number; + size_t count; +}; + +struct instruction_list { + union instruction_or_arg *instructions; + size_t len; + size_t cap; + + int line; +}; + +const char *apfl_instruction_to_string(enum instruction); + +struct instruction_list *apfl_instructions_new(struct gc *, int line); +void apfl_instructions_deinit(struct apfl_allocator, struct instruction_list *); + +void apfl_gc_instructions_traverse(struct instruction_list *, gc_visitor, void *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/compile.c b/src/compile.c new file mode 100644 index 0000000..ccc8eea --- /dev/null +++ b/src/compile.c @@ -0,0 +1,349 @@ +#include +#include +#include +#include + +#include "apfl.h" +#include "compile.h" + +#include "bytecode.h" +#include "resizable.h" + +#define DEBUG_COMPILING 1 + +#if DEBUG_COMPILING +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__); +#else +# define DEBUG_LOG(...) +#endif + +struct compiler { + struct gc *gc; + struct apfl_error error; + size_t line; +}; + +#define TRY(b) do { if (!(b)) { return false; } } while(0) +#define MALLOC_FAIL_IF_NULL(compiler, x) \ + do { \ + if ((x) == NULL) { \ + compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); \ + return false; \ + } \ + } while (0) + +static bool compile_expr(struct compiler *, struct apfl_expr *, struct instruction_list *); + +static bool +malloc_failure_on_false(struct compiler *compiler, bool b) +{ + if (!b) { + compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + } + return b; +} + +static bool +ilist_ensure_cap(struct compiler *compiler, struct instruction_list *ilist, size_t n) +{ + return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements( + compiler->gc->allocator, + sizeof(union instruction_or_arg), + (void **)&ilist->instructions, + ilist->len, + &ilist->cap, + n + )); +} + +static void +ilist_put_insn(struct instruction_list *ilist, enum instruction instruction) +{ + assert(ilist->cap > 0); + assert(ilist->len < ilist->cap); + + ilist->instructions[ilist->len] = (union instruction_or_arg) { + .instruction = instruction, + }; + ilist->len++; + + DEBUG_LOG("put_insn: %s\n", apfl_instruction_to_string(instruction)); +} + +static void +ilist_put_number(struct instruction_list *ilist, apfl_number number) +{ + assert(ilist->cap > 0); + assert(ilist->len < ilist->cap); + + ilist->instructions[ilist->len] = (union instruction_or_arg) { + .number = number + }; + ilist->len++; + + DEBUG_LOG("put_number: %lf\n", number); +} + +static void +ilist_put_count(struct instruction_list *ilist, size_t count) +{ + assert(ilist->cap > 0); + assert(ilist->len < ilist->cap); + + ilist->instructions[ilist->len] = (union instruction_or_arg) { + .count = count + }; + ilist->len++; + + DEBUG_LOG("put_count: %d\n", (int)count); +} + +static void +ilist_put_string(struct instruction_list *ilist, struct apfl_string *string) +{ + assert(ilist->cap > 0); + assert(ilist->len < ilist->cap); + + ilist->instructions[ilist->len] = (union instruction_or_arg) { + .string = string + }; + ilist->len++; + + DEBUG_LOG("put_string: " APFL_STR_FMT "\n", APFL_STR_FMT_ARGS(*string)); +} + +static bool +string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in) +{ + struct apfl_string *str = apfl_gc_new_string(compiler->gc); + if (str == NULL) { + compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + return false; + } + + *str = apfl_string_move(in); + *out = str; + return true; +} + +static bool +compile_constant(struct compiler *compiler, struct apfl_expr_const *constant, struct instruction_list *ilist) +{ + switch (constant->type) { + case APFL_EXPR_CONST_NIL: + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_NIL); + return true; + case APFL_EXPR_CONST_BOOLEAN: + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, constant->boolean ? INSN_TRUE : INSN_FALSE); + return true; + case APFL_EXPR_CONST_NUMBER: + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_NUMBER); + ilist_put_number(ilist, constant->number); + return true; + case APFL_EXPR_CONST_STRING: + TRY(ilist_ensure_cap(compiler, ilist, 2)); + + struct apfl_string *str; + TRY(string_move_into_new(compiler, &str, &constant->string)); + + ilist_put_insn(ilist, INSN_STRING); + ilist_put_string(ilist, str); + return true; + } + + assert(false); + return false; +} + +static bool +compile_list(struct compiler *compiler, struct apfl_expr_list *list, struct instruction_list *ilist) +{ + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_LIST); + ilist_put_count(ilist, list->len); + + for (size_t i = 0; i < list->len; i++) { + struct apfl_expr_list_item *item = &list->items[i]; + + TRY(compile_expr(compiler, item->expr, ilist)); + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, item->expand ? INSN_LIST_EXPAND_INTO : INSN_LIST_APPEND); + } + + return true; +} + +static bool +compile_dict(struct compiler *compiler, struct apfl_expr_dict *dict, struct instruction_list *ilist) +{ + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_DICT); + + for (size_t i = 0; i < dict->len; i++) { + struct apfl_expr_dict_pair *pair = &dict->items[i]; + + TRY(compile_expr(compiler, pair->k, ilist)); + TRY(compile_expr(compiler, pair->v, ilist)); + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_DICT_APPEND_KVPAIR); + } + + return true; +} + +static bool +compile_dot(struct compiler *compiler, struct apfl_expr_dot *dot, struct instruction_list *ilist) +{ + TRY(compile_expr(compiler, dot->lhs, ilist)); + + TRY(ilist_ensure_cap(compiler, ilist, 3)); + + struct apfl_string *str; + TRY(string_move_into_new(compiler, &str, &dot->rhs)); + + ilist_put_insn(ilist, INSN_STRING); + ilist_put_string(ilist, str); + ilist_put_insn(ilist, INSN_GET_MEMBER); + + return true; +} + +static bool +compile_at(struct compiler *compiler, struct apfl_expr_at *at, struct instruction_list *ilist) +{ + TRY(compile_expr(compiler, at->lhs, ilist)); + TRY(compile_expr(compiler, at->rhs, ilist)); + + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_GET_MEMBER); + + return true; +} + +static bool +compile_var(struct compiler *compiler, struct apfl_string *var, struct instruction_list *ilist) +{ + 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_GET); + ilist_put_string(ilist, str); + + return true; +} + +static bool +compile_simple_assignment( + struct compiler *compiler, + struct apfl_string *var, + struct apfl_expr *rhs, + struct instruction_list *ilist +) { + 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_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_string(ilist, str); + + return true; +} + +static bool +compile_assignment( + struct compiler *compiler, + struct apfl_expr_assignment *assignment, + struct instruction_list *ilist +) { + if ( + assignment->lhs.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER + && assignment->lhs.var_or_member.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR + ) { + return compile_simple_assignment( + compiler, + &assignment->lhs.var_or_member.var, + assignment->rhs, + ilist + ); + } + + // TODO: Implement other assignables + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); + return false; +} + +static bool +compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist) +{ + size_t new_line = (size_t)expr->position.line; + if (new_line != compiler->line) { + if (new_line == compiler->line + 1) { + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_NEXT_LINE); + } else { + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_SET_LINE); + ilist_put_count(ilist, new_line); + } + + compiler->line = new_line; + } + + switch (expr->type) { + case APFL_EXPR_CALL: + case APFL_EXPR_SIMPLE_FUNC: + case APFL_EXPR_COMPLEX_FUNC: + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); + return false; + case APFL_EXPR_CONSTANT: + return compile_constant(compiler, &expr->constant, ilist); + case APFL_EXPR_BLANK: + TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_NIL); + return true; + case APFL_EXPR_LIST: + return compile_list(compiler, &expr->list, ilist); + case APFL_EXPR_DICT: + return compile_dict(compiler, &expr->dict, ilist); + case APFL_EXPR_DOT: + return compile_dot(compiler, &expr->dot, ilist); + case APFL_EXPR_AT: + return compile_at(compiler, &expr->at, ilist); + case APFL_EXPR_VAR: + return compile_var(compiler, &expr->var, ilist); + case APFL_EXPR_ASSIGNMENT: + return compile_assignment(compiler, &expr->assignment, ilist); + } + + assert(false); + return false; +} + +bool +apfl_compile(struct gc *gc, struct apfl_expr expr, struct apfl_error *error_out, struct instruction_list *out) +{ + struct compiler compiler = { + .gc = gc, + .line = out->line, + }; + + bool ok = compile_expr(&compiler, &expr, out); + apfl_expr_deinit(gc->allocator, &expr); + + if (!ok) { + *error_out = compiler.error; + } + return ok; +} diff --git a/src/compile.h b/src/compile.h new file mode 100644 index 0000000..5ba7ac5 --- /dev/null +++ b/src/compile.h @@ -0,0 +1,19 @@ +#ifndef APFL_COMPILE_H +#define APFL_COMPILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "apfl.h" + +#include "bytecode.h" +#include "gc.h" + +bool apfl_compile(struct gc *, struct apfl_expr, struct apfl_error *, struct instruction_list *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..fd4bde0 --- /dev/null +++ b/src/context.c @@ -0,0 +1,550 @@ +#include +#include +#include + +#include "apfl.h" +#include "alloc.h" +#include "context.h" +#include "gc.h" +#include "hashmap.h" +#include "resizable.h" +#include "value.h" + +typedef struct apfl_value *variable; + +static struct stack * +stack_new(struct gc *gc) +{ + struct stack *stack = apfl_gc_new_stack(gc); + if (stack == NULL) { + return NULL; + } + + *stack = (struct stack) { + .items = NULL, + .len = 0, + .cap = 0, + }; + + return stack; +} + +void +apfl_stack_deinit(struct apfl_allocator allocator, struct stack *stack) +{ + FREE_LIST(allocator, stack->items, stack->cap); +} + +bool +apfl_stack_push(apfl_ctx ctx, struct apfl_value value) +{ + return apfl_resizable_append( + ctx->gc.allocator, + sizeof(struct apfl_value), + (void **)&ctx->stack->items, + &ctx->stack->len, + &ctx->stack->cap, + &value, + 1 + ); +} + +bool +apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index) +{ + if (*index < 0) { + if ((size_t)-*index > ctx->stack->len) { + return false; + } + *index = ctx->stack->len + *index; + } else if ((size_t)*index >= ctx->stack->len) { + return false; + } + + assert(0 <= *index && (size_t)*index < ctx->stack->len); + + return true; +} + +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) +{ + for (size_t i = 0; i < count; i++) { + if (!apfl_stack_check_index(ctx, &indices[i])) { + return false; + } + } + + qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx); + + for (size_t i = count; i-- > 0; ) { + // Will not fail, as we've already checked the indices + assert(apfl_resizable_cut_without_resize( + sizeof(struct apfl_value), + (void **)&ctx->stack->items, + &ctx->stack->len, + indices[i], + 1 + )); + } + + // TODO: Shrink stack + + return true; +} + +bool +apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) +{ + if (!apfl_stack_check_index(ctx, &index)) { + return false; + } + + *value = ctx->stack->items[index]; + + assert(apfl_resizable_splice( + ctx->gc.allocator, + sizeof(struct apfl_value), + (void **)ctx->stack, + &ctx->stack->len, + &ctx->stack->cap, + index, + 1, + NULL, + 0 + )); + + return true; +} + +static struct apfl_value * +stack_get_pointer(apfl_ctx ctx, apfl_stackidx index) +{ + if (!apfl_stack_check_index(ctx, &index)) { + return NULL; + } + + return &ctx->stack->items[index]; +} + +static bool +stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index) +{ + if (!apfl_stack_check_index(ctx, index)) { + return false; + } + + *value = ctx->stack->items[*index]; + + return true; +} + +bool +apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) +{ + return stack_get_and_adjust_index(ctx, value, &index); +} + +static struct apfl_value * +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_stack_drop(apfl_ctx ctx, apfl_stackidx index) +{ + struct apfl_value value; + return apfl_stack_pop(ctx, &value, index); +} + +static bool +scope_keys_eq(void *opaque, const void *_a, const void *_b) +{ + (void)opaque; + const struct apfl_string * const *a = _a; + const struct apfl_string * const *b = _b; + + return apfl_string_eq(**a, **b); +} + +static apfl_hash +scope_calc_hash(void *opaque, const void *_key) +{ + (void)opaque; + const struct apfl_string *const*key = _key; + struct apfl_string_view sv = apfl_string_view_from(**key); + return apfl_hash_fnv1a(sv.bytes, sv.len); +} + +static struct scope * +scope_new(struct gc *gc) +{ + struct apfl_hashmap map; + if (!apfl_hashmap_init( + &map, + gc->allocator, + (struct apfl_hashmap_callbacks) { + .opaque = NULL, + .keys_eq = scope_keys_eq, + .calc_hash = scope_calc_hash, + }, + sizeof(struct apfl_string *), + sizeof(variable) + )) { + return NULL; + } + + struct scope *scope = apfl_gc_new_scope(gc); + if (scope == NULL) { + apfl_hashmap_deinit(&map); + return NULL; + } + + *scope = (struct scope) { + .map = map, + }; + + return scope; +} + +void +apfl_scope_deinit(struct apfl_allocator allocator, struct scope *scope) +{ + (void)allocator; + apfl_hashmap_deinit(&scope->map); +} + +void +apfl_gc_var_traverse(struct apfl_value *var, gc_visitor cb, void *opaque) +{ + struct gc_object *child = apfl_value_get_gc_object(*var); + if (child != NULL) { + cb(opaque, child); + } +} + +void +apfl_gc_scope_traverse(struct scope *scope, gc_visitor cb, void *opaque) +{ + HASHMAP_EACH(&scope->map, cur) { + struct apfl_string **k; + apfl_hashmap_cursor_peek_key(cur, (void **)&k); + cb(opaque, GC_OBJECT_FROM(*k, GC_TYPE_STRING)); + + variable *v; + apfl_hashmap_cursor_peek_value(cur, (void **)&v); + cb(opaque, GC_OBJECT_FROM(*v, GC_TYPE_VAR)); + } +} + +bool +apfl_scope_get(struct scope *scope, struct apfl_string *name, struct apfl_value *out) +{ + variable var; + if (!apfl_hashmap_get(&scope->map, &name, &var)) { + return false; + } + + // The value is now in the variable and outside of it (presumably to be + // saved onto the stack). We need to set the COW flag so a mutation of one + // copy doesn't affect the other one. + *out = apfl_value_set_cow_flag(*var); + + return true; +} + +static variable +get_or_create_variable(struct gc *gc, struct scope *scope, struct apfl_string *name) +{ + variable var; + if (apfl_hashmap_get(&scope->map, &name, &var)) { + return var; + } + + struct apfl_hashmap_prepared_set prepared_set; + if (!apfl_hashmap_prepare_set(&scope->map, &prepared_set, &name)) { + return NULL; + } + + var = apfl_gc_new_var(gc); + if (var == NULL) { + return NULL; + } + + *var = (struct apfl_value) { .type = VALUE_NIL }; + apfl_hashmap_set_prepared(prepared_set, &var); + + return var; +} + +bool +apfl_scope_set(struct gc *gc, struct scope *scope, struct apfl_string *name, struct apfl_value value) +{ + variable var = get_or_create_variable(gc, scope, name); + if (var == NULL) { + 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; +} + +apfl_ctx +apfl_ctx_new(struct apfl_allocator base_allocator) +{ + apfl_ctx ctx = ALLOC_OBJ(base_allocator, struct apfl_ctx_data); + if (ctx == NULL) { + return NULL; + } + + if (!apfl_gc_init(&ctx->gc, base_allocator)) { + FREE_OBJ(base_allocator, ctx); + return NULL; + } + + if ((ctx->scope = scope_new(&ctx->gc)) == NULL) { + goto error; + } + + if (!apfl_gc_root_add(&ctx->gc, GC_OBJECT_FROM(ctx->scope, GC_TYPE_SCOPE))) { + goto error; + } + + if ((ctx->stack = stack_new(&ctx->gc)) == NULL) { + goto error; + } + + if (!apfl_gc_root_add(&ctx->gc, GC_OBJECT_FROM(ctx->stack, GC_TYPE_STACK))) { + goto error; + } + + return ctx; + +error: + apfl_ctx_destroy(ctx); + return NULL; +} + +void +apfl_ctx_destroy(apfl_ctx ctx) +{ + if (ctx == NULL) { + return; + } + + struct apfl_allocator base_allocator = ctx->gc.base_allocator; + + apfl_gc_full(&ctx->gc); + apfl_gc_deinit(&ctx->gc); + + FREE_OBJ(base_allocator, ctx); +} + +#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \ + struct apfl_value *value = stack_push_placeholder(ctx); \ + if (value == NULL) { \ + return APFL_RESULT_ERR_FATAL; \ + } \ + \ + struct apfl_value new_value = {.type = TYPE}; \ + if ((new_value.MEMB = NEW) == NULL) { \ + assert(apfl_stack_drop(ctx, -1)); \ + return APFL_RESULT_ERR_FATAL; \ + } \ + \ + *value = new_value; \ + \ + return APFL_RESULT_OK; + +enum apfl_result +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) + ) +} + +enum apfl_result +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) { + return APFL_RESULT_ERR; + } + + if (list_val->type != VALUE_LIST) { + return APFL_RESULT_ERR; + } + + struct apfl_value value; + if (!apfl_stack_get(ctx, &value, value_index)) { + return APFL_RESULT_ERR; + } + + enum apfl_result result = apfl_list_splice( + &ctx->gc, + &list_val->list, + list_val->list->len, + 0, + &value, + 1 + ) + ? APFL_RESULT_OK + : APFL_RESULT_ERR; + + assert(apfl_stack_drop(ctx, value_index)); + + return result; +} + +enum apfl_result +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) { + return APFL_RESULT_ERR; + } + + if (dst_val->type != VALUE_LIST) { + return APFL_RESULT_ERR; + } + + struct apfl_value src_val; + if (!apfl_stack_get(ctx, &src_val, src_index)) { + return APFL_RESULT_ERR; + } + + if (dst_val->type != VALUE_LIST) { + assert(apfl_stack_drop(ctx, src_index)); + return APFL_RESULT_ERR; + } + + enum apfl_result result = apfl_list_splice( + &ctx->gc, + &dst_val->list, + dst_val->list->len, + 0, + src_val.list->items, + src_val.list->len + ) + ? APFL_RESULT_OK + : APFL_RESULT_ERR; + + assert(apfl_stack_drop(ctx, src_index)); + return result; +} + +enum apfl_result +apfl_dict_create(apfl_ctx ctx) +{ + CREATE_GC_OBJECT_VALUE_ON_STACK( + ctx, + VALUE_DICT, + dict, + apfl_dict_new(&ctx->gc) + ) +} + +enum apfl_result +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 + ) { + return APFL_RESULT_ERR; + } + + if (dict_value->type != VALUE_DICT) { + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index})); + return APFL_RESULT_ERR; + } + + if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) { + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index})); + return APFL_RESULT_ERR; + } + + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index})); + return APFL_RESULT_OK; +} + +static enum apfl_result +apfl_get_member_inner( + apfl_ctx ctx, + struct apfl_value container, + struct apfl_value k +) { + struct apfl_value *value = stack_push_placeholder(ctx); + if (value == NULL) { + return APFL_RESULT_ERR; + } + + if (apfl_value_get_item(container, k, value) != GET_ITEM_OK) { + assert(apfl_stack_drop(ctx, -1)); + return APFL_RESULT_ERR; + } + + return APFL_RESULT_OK; +} + +enum apfl_result +apfl_get_member( + apfl_ctx ctx, + apfl_stackidx container_index, + apfl_stackidx k_index +) { + 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) + ) { + return APFL_RESULT_ERR; + } + + enum apfl_result result = apfl_get_member_inner(ctx, container, k); + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index})); + return result; +} + +void +apfl_gc_stack_traverse(struct stack *stack, gc_visitor visitor, void *opaque) +{ + for (size_t i = 0; i < stack->len; i++) { + struct gc_object *object = apfl_value_get_gc_object(stack->items[i]); + if (object != NULL) { + visitor(opaque, object); + } + } +} diff --git a/src/context.h b/src/context.h new file mode 100644 index 0000000..c5e74ee --- /dev/null +++ b/src/context.h @@ -0,0 +1,57 @@ +#ifndef APFL_CONTEXT_H +#define APFL_CONTEXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "bytecode.h" +#include "hashmap.h" +#include "gc.h" +#include "value.h" + +struct stack { + struct apfl_value *items; + size_t len; + size_t cap; +}; + +struct scope { + struct apfl_hashmap map; +}; + +struct apfl_ctx_data { + struct gc gc; + + struct scope *scope; + struct stack *stack; + + int execution_line; +}; + +void apfl_stack_deinit(struct apfl_allocator, struct stack *); +void apfl_gc_stack_traverse(struct stack *, gc_visitor, void *); + +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_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); + + +void apfl_gc_var_traverse(struct apfl_value *, gc_visitor, void *); +void apfl_gc_scope_traverse(struct scope *, gc_visitor, void *); + +bool apfl_stack_push(apfl_ctx, struct apfl_value); +bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *); +bool apfl_stack_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx); +bool apfl_stack_get(apfl_ctx, struct apfl_value *value, apfl_stackidx); +bool apfl_stack_drop(apfl_ctx, apfl_stackidx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/error.c b/src/error.c index 49884aa..8621aa4 100644 --- a/src/error.c +++ b/src/error.c @@ -54,6 +54,8 @@ apfl_error_type_name(enum apfl_error_type type) return "APFL_ERR_UNEXPECTED_EXPR_IN_MEMBER_ACCESS"; case APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS: return "APFL_ERR_UNEXPECTED_BLANK_IN_MEMBER_ACCESS"; + case APFL_ERR_NOT_IMPLEMENTED: + return "APFL_ERR_NOT_IMPLEMENTED"; } return ""; @@ -177,6 +179,11 @@ apfl_error_print(struct apfl_error error, FILE *file) POSARGS ); break; + case APFL_ERR_NOT_IMPLEMENTED: + fprintf( + file, + "Feature not implemented\n" + ); } fprintf(file, "Unknown error %d\n", (int)error.type); diff --git a/src/eval.c b/src/eval.c index e27266e..e9c4bfe 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3,9 +3,11 @@ #include "apfl.h" #include "alloc.h" +#include "bytecode.h" +#include "compile.h" +#include "context.h" #include "hashmap.h" #include "internal.h" -#include "resizable.h" #include "value.h" #define TRY(ex) \ @@ -16,1393 +18,177 @@ } \ } while (0) -struct apfl_ctx_data { - struct apfl_allocator allocator; - - struct apfl_hashmap scope; - - struct apfl_value *stack; - size_t stack_len; - size_t stack_cap; -}; - -struct variable_data { - unsigned refcount; - struct apfl_value value; -}; - -typedef struct variable_data *variable; - -enum match_result { - MATCH_OK, - MATCH_DOESNT_MATCH, - MATCH_ERROR, - MATCH_FATAL_ERROR, - MATCH_NOT_YET_IMPLEMENTED, -}; - -enum match_pattern_type { - MPATTERN_BLANK, - MPATTERN_VALUE, - MPATTERN_LIST, -}; - -struct match_pattern_value { - apfl_refcounted_string varname; - variable var; - struct apfl_value value; - struct apfl_value *member_keys; - size_t member_keys_len; - size_t member_keys_cap; -}; - -struct match_pattern_list { - struct match_pattern *subpatterns; - size_t subpatterns_len; - bool with_expand; - size_t expand_index; -}; - -enum match_pattern_constraint_type { - MPATTERN_CONSTRAINT_EQUALS, - MPATTERN_CONSTRAINT_PREDICATE, -}; - -struct match_pattern_constraint { - enum match_pattern_constraint_type type; - struct apfl_value value; -}; - -struct match_pattern { - enum match_pattern_type type; - union { - struct match_pattern_value value; - struct match_pattern_list list; - }; - struct match_pattern_constraint *constraints; - size_t constraints_len; - size_t constraints_cap; -}; - -static enum apfl_result evaluate(apfl_ctx, struct apfl_expr *); -static variable variable_new(apfl_ctx); -static variable variable_incref(variable var); -static void variable_unref(apfl_ctx, variable var); -static enum match_result match_pattern_from_assignable(apfl_ctx ctx, struct apfl_expr_assignable *, struct match_pattern *); -static void match_pattern_deinit(apfl_ctx, struct match_pattern *); -static enum match_result match_pattern_match(struct apfl_allocator, struct match_pattern *, struct apfl_value value); - -static bool -scope_keys_eq(void *opaque, const void *_a, const void *_b) +static enum apfl_result +stack_push_or_fatal(apfl_ctx ctx, struct apfl_value value) { - (void)opaque; - apfl_refcounted_string const *a = _a; - apfl_refcounted_string const *b = _b; - - return *a == *b || apfl_string_eq(*a, *b); -} - -static apfl_hash -scope_calc_hash(void *opaque, const void *_key) -{ - (void)opaque; - apfl_refcounted_string const *key = _key; - struct apfl_string_view sv = apfl_string_view_from(*key); - return apfl_hash_fnv1a(sv.bytes, sv.len); -} - -static void -scope_destroy_key(void *opaque, void *_key) -{ - apfl_ctx ctx = opaque; - apfl_refcounted_string *key = _key; - - apfl_refcounted_string_unref(ctx->allocator, *key); -} - -static void -scope_destroy_value(void *opaque, void *_value) -{ - apfl_ctx ctx = opaque; - variable *value = _value; - variable_unref(ctx, *value); -} - -static void -scope_copy_key(void *opaque, void *_dest, void *_src) -{ - (void)opaque; - apfl_refcounted_string *dest = _dest; - apfl_refcounted_string *src = _src; - *dest = apfl_refcounted_string_incref(*src); -} - -static void -scope_copy_value(void *opaque, void *_dest, void *_src) -{ - (void)opaque; - variable *dest = _dest; - variable *src = _src; - *dest = variable_incref(*src); -} - -bool -scope_init(apfl_ctx ctx, struct apfl_hashmap *map) -{ - return apfl_hashmap_init( - map, - ctx->allocator, - (struct apfl_hashmap_callbacks) { - .opaque = ctx, - .keys_eq = scope_keys_eq, - .calc_hash = scope_calc_hash, - .destroy_key = scope_destroy_key, - .destroy_value = scope_destroy_value, - .copy_key = scope_copy_key, - .copy_value = scope_copy_value, - }, - sizeof(apfl_refcounted_string), - sizeof(variable) - ); -} - -static variable -variable_new(apfl_ctx ctx) -{ - variable var = ALLOC_OBJ(ctx->allocator, struct variable_data); - if (var == NULL) { - return NULL; - } - - var->refcount = 1; - var->value.type = VALUE_NIL; - - return var; -} - -static variable -variable_incref(variable var) -{ - if (var != NULL) { - var->refcount++; - } - return var; -} - -static void -variable_unref(apfl_ctx ctx, variable var) -{ - if (var != NULL && apfl_refcount_dec(&var->refcount)) { - apfl_value_deinit(ctx->allocator, &var->value); - FREE_OBJ(ctx->allocator, var); - } -} - -static void -variable_set(apfl_ctx ctx, variable var, struct apfl_value value) -{ - if (var == NULL) { - return; - } - - apfl_value_deinit(ctx->allocator, &var->value); - var->value = apfl_value_move(&value); -} - -static bool -stack_push(apfl_ctx ctx, struct apfl_value value) -{ - bool ok = apfl_resizable_append( - ctx->allocator, - sizeof(struct apfl_value), - (void **)&ctx->stack, - &ctx->stack_len, - &ctx->stack_cap, - &value, - 1 - ); - - if (!ok) { - apfl_value_deinit(ctx->allocator, &value); - } - - return ok; -} - -static bool -stack_check_index(apfl_ctx ctx, apfl_stackidx *index) -{ - if (*index < 0) { - if ((size_t)-*index > ctx->stack_len) { - return false; - } - *index = ctx->stack_len + *index; - } else if ((size_t)*index >= ctx->stack_len) { - return false; - } - - assert(0 <= *index && (size_t)*index < ctx->stack_len); - - return true; -} - -static bool -stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) -{ - if (!stack_check_index(ctx, &index)) { - return false; - } - - *value = ctx->stack[index]; - - assert(apfl_resizable_splice( - ctx->allocator, - sizeof(struct apfl_value), - (void **)ctx->stack, - &ctx->stack_len, - &ctx->stack_cap, - index, - 1, - NULL, - 0 - )); - - return true; -} - -static struct apfl_value -stack_must_pop(apfl_ctx ctx, apfl_stackidx index) -{ - struct apfl_value value; - assert(stack_pop(ctx, &value, index)); - return value; -} - -static bool -stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index) -{ - if (!stack_check_index(ctx, &index)) { - return false; - } - - *value = apfl_value_incref(ctx->stack[index]); - - return true; -} - -static struct apfl_value -stack_must_get(apfl_ctx ctx, apfl_stackidx index) -{ - struct apfl_value value; - assert(stack_get(ctx, &value, index)); - return value; -} - -static bool -stack_drop(apfl_ctx ctx, apfl_stackidx index) -{ - struct apfl_value value; - if (!stack_pop(ctx, &value, index)) { - return false; - } - apfl_value_deinit(ctx->allocator, &value); - return true; + return apfl_stack_push(ctx, value) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static void stack_must_drop(apfl_ctx ctx, apfl_stackidx index) { - assert(stack_drop(ctx, index)); + assert(apfl_stack_drop(ctx, index)); } -apfl_ctx -apfl_ctx_new(struct apfl_allocator allocator) +static enum apfl_result +get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg) { - apfl_ctx ctx = ALLOC_OBJ(allocator, struct apfl_ctx_data); - - if (ctx == NULL) { - return NULL; + if (*i >= ilist->len) { + return APFL_RESULT_ERR; } - ctx->allocator = allocator; - ctx->stack = NULL; - ctx->stack_len = 0; - ctx->stack_cap = 0; - - if (!scope_init(ctx, &ctx->scope)) { - FREE_OBJ(allocator, ctx); - return NULL; - } - - - return ctx; + *arg = ilist->instructions[(*i)++]; + return APFL_RESULT_OK; } -void -apfl_ctx_destroy(apfl_ctx ctx) +static enum apfl_result +variable_get(apfl_ctx ctx, struct apfl_string *name) { - if (ctx == NULL) { - return; - } - - apfl_hashmap_deinit(&ctx->scope); - while (ctx->stack_len > 0) { - stack_must_drop(ctx, -1); - } - FREE_LIST(ctx->allocator, ctx->stack, ctx->stack_cap); - FREE_OBJ(ctx->allocator, ctx); -} - -static variable -ctx_get_var_for_assignment_inner(apfl_ctx ctx, /*borrowed*/ apfl_refcounted_string name) -{ - variable var; - if (apfl_hashmap_get(&ctx->scope, &name, &var)) { - return var; - } - - if ((var = variable_new(ctx)) == NULL) { - return NULL; - } - - if (!apfl_hashmap_set(&ctx->scope, &name, &var)) { - variable_unref(ctx, var); - return NULL; - } - - return var; -} - -static variable -ctx_get_var_for_assignment(apfl_ctx ctx, apfl_refcounted_string name) -{ - variable var = ctx_get_var_for_assignment_inner(ctx, name); - apfl_refcounted_string_unref(ctx->allocator, name); - return var; -} - -static variable -ctx_get_var(apfl_ctx ctx, apfl_refcounted_string name) -{ - variable var; - bool ok = apfl_hashmap_get(&ctx->scope, &name, &var); - apfl_refcounted_string_unref(ctx->allocator, name); - return ok ? var : NULL; -} - -static struct apfl_value -constant_to_value(struct apfl_expr_const *constant) -{ - switch (constant->type) { - case APFL_EXPR_CONST_NIL: - return (struct apfl_value) { .type = VALUE_NIL }; - case APFL_EXPR_CONST_BOOLEAN: - return (struct apfl_value) { - .type = VALUE_BOOLEAN, - .boolean = constant->boolean, - }; - case APFL_EXPR_CONST_STRING: - return (struct apfl_value) { - .type = VALUE_STRING, - .string = apfl_refcounted_string_incref(constant->string), - }; - case APFL_EXPR_CONST_NUMBER: - return (struct apfl_value) { - .type = VALUE_NUMBER, - .number = constant->number, - }; - } - - assert(false); - return (struct apfl_value) { .type = VALUE_NIL }; -} - -static bool -match_pattern_add_constraint( - struct apfl_allocator allocator, - struct match_pattern *pattern, - enum match_pattern_constraint_type type, - struct apfl_value value -) { - struct match_pattern_constraint constraint = { - .type = type, - .value = value, - }; - - if (!apfl_resizable_append( - allocator, - sizeof(struct match_pattern_constraint), - (void **)&pattern->constraints, - &pattern->constraints_len, - &pattern->constraints_cap, - &constraint, - 1 - )) { - apfl_value_deinit(allocator, &value); - return false; - } - return true; -} - -static enum match_result -match_pattern_from_assignable_list( - apfl_ctx ctx, - struct apfl_expr_assignable_list *assignable_list, - struct match_pattern *pattern -) { - pattern->type = MPATTERN_LIST; - struct match_pattern_list *pattern_list = &pattern->list; - *pattern_list = (struct match_pattern_list) { - .subpatterns = NULL, - .subpatterns_len = 0, - .with_expand = false, - .expand_index = 0, - }; - - if (assignable_list->len == 0) { - return MATCH_OK; - } - - if ((pattern_list->subpatterns = ALLOC_LIST( - ctx->allocator, - struct match_pattern, - assignable_list->len - )) == NULL) { - return MATCH_FATAL_ERROR; - } - - for (size_t i = 0; i < assignable_list->len; i++) { - struct apfl_expr_assignable_list_item *item = &assignable_list->items[i]; - - if (item->expand) { - if (pattern_list->with_expand) { - return MATCH_ERROR; - } - - pattern_list->with_expand = true; - pattern_list->expand_index = i; - } - - enum match_result result = match_pattern_from_assignable( - ctx, - &item->assignable, - &pattern_list->subpatterns[i] - ); - - if (result != MATCH_OK) { - DEINIT_CAP_LIST_WITH_ARGS( - ctx->allocator, - pattern_list->subpatterns, - pattern_list->subpatterns_len, - assignable_list->len, - match_pattern_deinit, - ctx - ); - return result; - } - - pattern_list->subpatterns_len++; - } - - return MATCH_OK; -} - -static enum match_result -match_pattern_from_var_or_member( - apfl_ctx ctx, - struct apfl_expr_assignable_var_or_member *var_or_member, - struct match_pattern *pattern -) { - pattern->type = MPATTERN_VALUE; - pattern->value = (struct match_pattern_value) { - .varname = NULL, - .var = NULL, - .value = {.type = VALUE_NIL}, - .member_keys = NULL, - .member_keys_len = 0, - .member_keys_cap = 0, - }; - - enum match_result result; - enum apfl_result eval_result; - struct apfl_value str_value; - struct apfl_value rhs_value; - -next: - switch (var_or_member->type) { - case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR: - pattern->value.varname = apfl_refcounted_string_incref(var_or_member->var); - return MATCH_OK; - case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT: - str_value.type = VALUE_STRING; - str_value.string = apfl_refcounted_string_incref(var_or_member->dot.rhs); - - if (!apfl_resizable_append( - ctx->allocator, - sizeof(struct apfl_value), - (void **)&pattern->value.member_keys, - &pattern->value.member_keys_len, - &pattern->value.member_keys_cap, - &str_value, - 1 - )) { - apfl_value_deinit(ctx->allocator, &str_value); - result = MATCH_FATAL_ERROR; - goto fail; - } - - var_or_member = var_or_member->dot.lhs; - goto next; - case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT: - eval_result = evaluate(ctx, var_or_member->at.rhs); - switch (eval_result) { - case APFL_RESULT_OK: - break; - case APFL_RESULT_ERR: - result = MATCH_ERROR; - goto fail; - case APFL_RESULT_ERR_FATAL: - result = MATCH_FATAL_ERROR; - goto fail; - } - - rhs_value = stack_must_pop(ctx, -1); - - if (!apfl_resizable_append( - ctx->allocator, - sizeof(struct apfl_value), - (void **)&pattern->value.member_keys, - &pattern->value.member_keys_len, - &pattern->value.member_keys_cap, - &rhs_value, - 1 - )) { - apfl_value_deinit(ctx->allocator, &rhs_value); - result = MATCH_FATAL_ERROR; - goto fail; - } - - var_or_member = var_or_member->at.lhs; - goto next; - } - -fail: - DEINIT_LIST( - ctx->allocator, - pattern->value.member_keys, - pattern->value.member_keys_cap, - apfl_value_deinit - ); - return result; -} - - -static enum match_result -match_pattern_from_assignable_inner( - apfl_ctx ctx, - struct apfl_expr_assignable *assignable, - struct match_pattern *pattern -) { struct apfl_value value; - - pattern->type = MPATTERN_BLANK; - pattern->constraints = NULL; - pattern->constraints_len = 0; - pattern->constraints_cap = 0; - -next: - switch (assignable->type) { - case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER: - return match_pattern_from_var_or_member(ctx, &assignable->var_or_member, pattern); - case APFL_EXPR_ASSIGNABLE_CONSTANT: - if (!match_pattern_add_constraint( - ctx->allocator, - pattern, - MPATTERN_CONSTRAINT_EQUALS, - constant_to_value(&assignable->constant) - )) { - return MATCH_FATAL_ERROR; - } - pattern->type = MPATTERN_BLANK; - return MATCH_OK; - case APFL_EXPR_ASSIGNABLE_PREDICATE: - switch (evaluate(ctx, assignable->predicate.rhs)) { - case APFL_RESULT_OK: - break; - case APFL_RESULT_ERR: - return MATCH_ERROR; - case APFL_RESULT_ERR_FATAL: - return MATCH_FATAL_ERROR; - } - - if (!stack_pop(ctx, &value, -1)) { - return MATCH_FATAL_ERROR; - } - - if (!match_pattern_add_constraint( - ctx->allocator, - pattern, - MPATTERN_CONSTRAINT_PREDICATE, - apfl_value_move(&value) - )) { - return MATCH_FATAL_ERROR; - } - - assignable = assignable->predicate.lhs; - goto next; - case APFL_EXPR_ASSIGNABLE_LIST: - return match_pattern_from_assignable_list(ctx, &assignable->list, pattern); - case APFL_EXPR_ASSIGNABLE_BLANK: - pattern->type = MPATTERN_BLANK; - return MATCH_OK; + if (!apfl_scope_get(ctx->scope, name, &value)) { + return APFL_RESULT_ERR; } - assert(false); - return MATCH_FATAL_ERROR; + return stack_push_or_fatal(ctx, value); } -static enum match_result -match_pattern_from_assignable( - apfl_ctx ctx, - struct apfl_expr_assignable *assignable, - struct match_pattern *pattern -) { - enum match_result result = match_pattern_from_assignable_inner(ctx, assignable, pattern); - if (result != MATCH_OK) { - match_pattern_deinit(ctx, pattern); - } - return result; -} - -static bool -match_pattern_create_vars(apfl_ctx ctx, struct match_pattern *pattern) +static enum apfl_result +variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack) { - switch (pattern->type) { - case MPATTERN_BLANK: - return true; - case MPATTERN_VALUE: - if (pattern->value.var != NULL) { - return true; - } - - if ((pattern->value.var = ctx_get_var_for_assignment( - ctx, - apfl_refcounted_string_incref(pattern->value.varname) - )) == NULL) { - return false; - } - - return true; - case MPATTERN_LIST: - for (size_t i = 0; i < pattern->list.subpatterns_len; i++) { - if (!match_pattern_create_vars(ctx, &pattern->list.subpatterns[i])) { - return false; - } - } - return true; - } - - assert(false); - return false; -} - -static void -match_pattern_constraint_deinit(struct apfl_allocator allocator, struct match_pattern_constraint *constraint) -{ - if (constraint == NULL) { - return; - } - - apfl_value_deinit(allocator, &constraint->value); -} - -static void -match_pattern_deinit(apfl_ctx ctx, struct match_pattern *pattern) -{ - if (pattern == NULL) { - return; - } - - switch (pattern->type) { - case MPATTERN_BLANK: - break; - case MPATTERN_VALUE: - apfl_refcounted_string_unref(ctx->allocator, pattern->value.varname); - variable_unref(ctx, pattern->value.var); - apfl_value_deinit(ctx->allocator, &pattern->value.value); - DEINIT_LIST( - ctx->allocator, - pattern->value.member_keys, - pattern->value.member_keys_cap, - apfl_value_deinit - ); - break; - case MPATTERN_LIST: - DEINIT_LIST_WITH_ARGS( - ctx->allocator, - pattern->list.subpatterns, - pattern->list.subpatterns_len, - match_pattern_deinit, - ctx - ); - break; - } - - DEINIT_LIST( - ctx->allocator, - pattern->constraints, - pattern->constraints_cap, - match_pattern_constraint_deinit - ); -} - -static enum match_result -match_pattern_check_constraint( - struct match_pattern_constraint *constraint, - const struct apfl_value *value -) { - switch (constraint->type) { - case MPATTERN_CONSTRAINT_PREDICATE: - return MATCH_NOT_YET_IMPLEMENTED; - case MPATTERN_CONSTRAINT_EQUALS: - if (apfl_value_eq(constraint->value, *value)) { - return MATCH_OK; - } - return MATCH_DOESNT_MATCH; - } - - assert(false); - return MATCH_FATAL_ERROR; -} - -static enum match_result -match_pattern_match_list_inner( - struct apfl_allocator allocator, - struct match_pattern_list *pattern_list, - /*borrowed*/ apfl_list list -) { - size_t list_len = apfl_list_len(list); - - if (pattern_list->with_expand - ? (list_len < pattern_list->subpatterns_len - 1) - : (pattern_list->subpatterns_len != list_len) - ) { - return MATCH_DOESNT_MATCH; - } - - size_t limit = pattern_list->with_expand - ? pattern_list->expand_index - : pattern_list->subpatterns_len; - - for (size_t i = 0; i < limit; i++) { - struct apfl_value list_item; - // Will not fail, as i is a valid index for the list - assert(apfl_list_get_item( - allocator, - apfl_list_incref(list), - i, - &list_item - )); - - enum match_result result = match_pattern_match( - allocator, - &pattern_list->subpatterns[i], - apfl_value_move(&list_item) - ); - if (result != MATCH_OK) { - return result; - } - } - - if (!pattern_list->with_expand) { - return MATCH_OK; - } - - size_t tail_len = pattern_list->subpatterns_len - pattern_list->expand_index - 1; - - for (size_t i = 0; i < tail_len; i++) { - size_t subpattern_index = pattern_list->subpatterns_len - i - 1; - size_t list_index = list_len - i - 1; - - struct apfl_value list_item; - // Will not fail, as list_index is a valid index for the list - assert(apfl_list_get_item( - allocator, - apfl_list_incref(list), - list_index, - &list_item - )); - - enum match_result result = match_pattern_match( - allocator, - &pattern_list->subpatterns[subpattern_index], - apfl_value_move(&list_item) - ); - if (result != MATCH_OK) { - return result; - } - } - - apfl_editable_list mid_builder = apfl_editable_list_new(allocator); - if (mid_builder == NULL) { - return MATCH_ERROR; - } - - for (size_t i = pattern_list->expand_index; i < list_len - tail_len; i++) { - struct apfl_value list_item; - // Will not fail, as i is a valid index for the list - assert(apfl_list_get_item( - allocator, - apfl_list_incref(list), - i, - &list_item - )); - if (!apfl_editable_list_append( - mid_builder, - apfl_value_move(&list_item)) - ) { - apfl_editable_list_destroy(mid_builder); - return MATCH_FATAL_ERROR; - } - } - - apfl_list mid_list = apfl_editable_list_finalize(mid_builder); - if (mid_list == NULL) { - return MATCH_FATAL_ERROR; - } - - return match_pattern_match( - allocator, - &pattern_list->subpatterns[pattern_list->expand_index], - (struct apfl_value) { - .type = VALUE_LIST, - .list = mid_list, - } - ); -} - -static enum match_result -match_pattern_match_list( - struct apfl_allocator allocator, - struct match_pattern_list *pattern_list, - struct apfl_value value -) { - if (value.type != VALUE_LIST) { - apfl_value_deinit(allocator, &value); - return MATCH_DOESNT_MATCH; - } - apfl_list list = apfl_list_incref(value.list); - apfl_value_deinit(allocator, &value); - - enum match_result result = match_pattern_match_list_inner(allocator, pattern_list, list); - apfl_list_unref(allocator, list); - return result; -} - -static enum match_result -match_pattern_match(struct apfl_allocator allocator, struct match_pattern *pattern, struct apfl_value value) -{ - // We put the constraints into the list from the outside in, so we need - // to iterate in reverse order. - for (size_t i = pattern->constraints_len; i-- > 0; ) { - enum match_result result = match_pattern_check_constraint( - &pattern->constraints[i], - &value - ); - if (result != MATCH_OK) { - apfl_value_deinit(allocator, &value); - return result; - } - } - - switch (pattern->type) { - case MPATTERN_BLANK: - apfl_value_deinit(allocator, &value); - return MATCH_OK; - case MPATTERN_VALUE: - pattern->value.value = apfl_value_move(&value); - return MATCH_OK; - case MPATTERN_LIST: - return match_pattern_match_list(allocator, &pattern->list, apfl_value_move(&value)); - } - - assert(false); - return MATCH_FATAL_ERROR; -} - -static enum match_result -match_pattern_apply_value_keys( - struct apfl_allocator allocator, - struct match_pattern_value *pattern_value, - apfl_editable_dict ed, - size_t i -) { - struct apfl_value *key = &pattern_value->member_keys[i]; - - if (i == 0) { - if (!apfl_editable_dict_set( - ed, - apfl_value_incref(*key), - apfl_value_move(&pattern_value->value) - )) { - return MATCH_FATAL_ERROR; - } - return MATCH_OK; - } - - apfl_editable_dict next_ed; - struct apfl_value value; - if (apfl_editable_dict_get( - ed, - apfl_value_incref(*key), - &value - )) { - if (value.type != VALUE_DICT) { - apfl_value_deinit(allocator, &value); - return MATCH_ERROR; - } - - next_ed = apfl_editable_dict_new_from_dict(allocator, value.dict); + if (!apfl_stack_get(ctx, &value, -1)) { + return APFL_RESULT_ERR; + } + if (!apfl_scope_set(&ctx->gc, ctx->scope, name, value)) { + return APFL_RESULT_ERR_FATAL; + } + if (keep_on_stack) { + // If the value should be kept on the stack, the value is now in two + // places. We need to set the COW flag to prevent mutations of one copy + // affecting the other one. + value = apfl_value_set_cow_flag(value); } else { - // Be nice and create the missing dictionary - next_ed = apfl_editable_dict_new(allocator); + stack_must_drop(ctx, -1); } - - if (next_ed == NULL) { - return MATCH_FATAL_ERROR; - } - - enum match_result result = match_pattern_apply_value_keys( - allocator, - pattern_value, - next_ed, - i - 1 - ); - if (result != MATCH_OK) { - apfl_editable_dict_destroy(next_ed); - return result; - } - - apfl_dict next_dict = apfl_editable_dict_finalize(next_ed); - if (next_dict == NULL) { - return MATCH_FATAL_ERROR; - } - - if (!apfl_editable_dict_set( - ed, - apfl_value_incref(*key), - (struct apfl_value) { - .type = VALUE_DICT, - .dict = next_dict, - } - )) { - return MATCH_FATAL_ERROR; - } - - return MATCH_OK; + return APFL_RESULT_OK; } -static enum match_result -match_pattern_apply_value(apfl_ctx ctx, struct match_pattern_value *pattern_value) +static enum apfl_result +variable_new(apfl_ctx ctx, struct apfl_string *name) { - if (pattern_value->var == NULL) { - return false; + if (!apfl_scope_create_var(&ctx->gc, ctx->scope, name)) { + return APFL_RESULT_ERR_FATAL; } - - if (pattern_value->member_keys_len == 0) { - variable_set(ctx, pattern_value->var, apfl_value_move(&pattern_value->value)); - return MATCH_OK; - } - - if (pattern_value->var->value.type != VALUE_DICT) { - return MATCH_ERROR; - } - - apfl_editable_dict ed = apfl_editable_dict_new_from_dict( - ctx->allocator, - apfl_dict_incref(pattern_value->var->value.dict) - ); - if (ed == NULL) { - return MATCH_FATAL_ERROR; - } - - enum match_result result = match_pattern_apply_value_keys( - ctx->allocator, - pattern_value, - ed, - pattern_value->member_keys_len - 1 - ); - if (result != MATCH_OK) { - apfl_editable_dict_destroy(ed); - return result; - } - - apfl_dict d = apfl_editable_dict_finalize(ed); - if (d == NULL) { - return MATCH_FATAL_ERROR; - } - - variable_set(ctx, pattern_value->var, (struct apfl_value) { - .type = VALUE_DICT, - .dict = d, - }); - - return MATCH_OK; + return APFL_RESULT_OK; } -static enum match_result -match_pattern_apply(apfl_ctx ctx, struct match_pattern *pattern) +static enum apfl_result +evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist) { - switch (pattern->type) { - case MPATTERN_BLANK: - return MATCH_OK; - case MPATTERN_VALUE: - return match_pattern_apply_value(ctx, &pattern->value); - case MPATTERN_LIST: - for (size_t i = 0; i < pattern->list.subpatterns_len; i++) { - enum match_result result = match_pattern_apply( - ctx, - &pattern->list.subpatterns[i] - ); - if (result != MATCH_OK) { - return result; - } - } - return MATCH_OK; + union instruction_or_arg arg; + + assert(*i < ilist->len); + switch (ilist->instructions[(*i)++].instruction) { + case INSN_NIL: + return stack_push_or_fatal(ctx, (struct apfl_value) {.type = VALUE_NIL}); + case INSN_TRUE: + return stack_push_or_fatal(ctx, (struct apfl_value) { + .type = VALUE_BOOLEAN, + .boolean = true, + }); + case INSN_FALSE: + return stack_push_or_fatal(ctx, (struct apfl_value) { + .type = VALUE_BOOLEAN, + .boolean = true, + }); + case INSN_NUMBER: + TRY(get_argument(i, ilist, &arg)); + return stack_push_or_fatal(ctx, (struct apfl_value) { + .type = VALUE_NUMBER, + .number = arg.number, + }); + case INSN_STRING: + TRY(get_argument(i, ilist, &arg)); + return stack_push_or_fatal(ctx, (struct apfl_value) { + .type = VALUE_STRING, + .string = arg.string, + }); + case INSN_LIST: + TRY(get_argument(i, ilist, &arg)); + return apfl_list_create(ctx, arg.count); + case INSN_LIST_APPEND: + return apfl_list_append(ctx, -2, -1); + case INSN_LIST_EXPAND_INTO: + return apfl_list_append_list(ctx, -2, -1); + case INSN_DICT: + return apfl_dict_create(ctx); + case INSN_DICT_APPEND_KVPAIR: + return apfl_dict_set(ctx, -3, -2, -1); + case INSN_GET_MEMBER: + return apfl_get_member(ctx, -2, -1); + case INSN_VAR_NEW: + TRY(get_argument(i, ilist, &arg)); + return variable_new(ctx, arg.string); + case INSN_VAR_GET: + TRY(get_argument(i, ilist, &arg)); + return variable_get(ctx, arg.string); + case INSN_VAR_SET: + TRY(get_argument(i, ilist, &arg)); + return variable_set(ctx, arg.string, true); + case INSN_NEXT_LINE: + ctx->execution_line++; + return APFL_RESULT_OK; + case INSN_SET_LINE: + TRY(get_argument(i, ilist, &arg)); + ctx->execution_line = arg.count; + return APFL_RESULT_OK; } assert(false); - return MATCH_FATAL_ERROR; -} - -static enum apfl_result -evaluate_constant(apfl_ctx ctx, struct apfl_expr_const *constant) -{ - return stack_push(ctx, constant_to_value(constant)) - ? APFL_RESULT_OK - : APFL_RESULT_ERR_FATAL; -} - -static enum apfl_result -evaluate_list_expr_to_list(apfl_ctx ctx, struct apfl_expr *expr, apfl_editable_list elist) -{ - enum apfl_result result = evaluate(ctx, expr); - if (result != APFL_RESULT_OK) { - return result; - } - - struct apfl_value value; - if (!stack_pop(ctx, &value, -1)) { - return APFL_RESULT_ERR_FATAL; - } - - if (value.type != VALUE_LIST) { - apfl_value_deinit(ctx->allocator, &value); - return APFL_RESULT_ERR; - } - - apfl_list list = apfl_list_incref(value.list); - apfl_value_deinit(ctx->allocator, &value); - - if (!apfl_editable_list_append_list(elist, list)) { - return APFL_RESULT_ERR_FATAL; - } - - return APFL_RESULT_OK; -} - -static enum apfl_result -evaluate_expr_list_into_list(apfl_ctx ctx, struct apfl_expr_list *list, apfl_editable_list elist) -{ - for (size_t i = 0; i < list->len; i++) { - struct apfl_expr_list_item *item = &list->items[i]; - - if (item->expand) { - TRY(evaluate_list_expr_to_list(ctx, item->expr, elist)); - } else { - TRY(evaluate(ctx, item->expr)); - if (!apfl_editable_list_append(elist, stack_must_pop(ctx, -1))) { - return APFL_RESULT_ERR_FATAL; - } - } - } - - return APFL_RESULT_OK; -} - -static enum apfl_result -evaluate_list(apfl_ctx ctx, struct apfl_expr_list *list) -{ - apfl_editable_list elist = apfl_editable_list_new(ctx->allocator); - if (elist == NULL) { - return APFL_RESULT_ERR_FATAL; - } - - enum apfl_result result = evaluate_expr_list_into_list(ctx, list, elist); - if (result != APFL_RESULT_OK) { - apfl_editable_list_destroy(elist); - return result; - } - - apfl_list out = apfl_editable_list_finalize(elist); - if (out == NULL) { - return APFL_RESULT_ERR_FATAL; - } - - if (!stack_push(ctx, (struct apfl_value) { - .type = VALUE_LIST, - .list = out, - })) { - apfl_list_unref(ctx->allocator, out); - return APFL_RESULT_ERR_FATAL; - } - - return APFL_RESULT_OK; -} - -static enum apfl_result -evaluate_dict_into_editable( - apfl_ctx ctx, - struct apfl_expr_dict *dict, - apfl_editable_dict ed -) { - for (size_t i = 0; i < dict->len; i++) { - TRY(evaluate(ctx, dict->items[i].k)); - TRY(evaluate(ctx, dict->items[i].v)); - - struct apfl_value value = stack_must_pop(ctx, -1); - struct apfl_value key = stack_must_pop(ctx, -1); - - if (!apfl_editable_dict_set( - ed, - apfl_value_move(&key), - apfl_value_move(&value) - )) { - return APFL_RESULT_ERR_FATAL; - } - } - - return APFL_RESULT_OK; -} - -static enum apfl_result -evaluate_dict(apfl_ctx ctx, struct apfl_expr_dict *dict) -{ - apfl_editable_dict ed = apfl_editable_dict_new(ctx->allocator); - - if (ed == NULL) { - return APFL_RESULT_ERR_FATAL; - } - - enum apfl_result result = evaluate_dict_into_editable(ctx, dict, ed); - if (result != APFL_RESULT_OK) { - apfl_editable_dict_destroy(ed); - } - - apfl_dict out = apfl_editable_dict_finalize(ed); - if (out == NULL) { - return APFL_RESULT_ERR_FATAL; - } - - if (!stack_push(ctx, (struct apfl_value) { - .type = VALUE_DICT, - .dict = out, - })) { - apfl_dict_unref(ctx->allocator, out); - return APFL_RESULT_ERR_FATAL; - } - - return APFL_RESULT_OK; -} - -static enum apfl_result -evaluate_dot(apfl_ctx ctx, struct apfl_expr_dot *dot) -{ - TRY(evaluate(ctx, dot->lhs)); - struct apfl_value lhs = stack_must_pop(ctx, -1); - - struct apfl_value key = (struct apfl_value) { - .type = VALUE_STRING, - .string = apfl_refcounted_string_incref(dot->rhs), - }; - - struct apfl_value out; - if (apfl_value_get_item(ctx->allocator, apfl_value_move(&lhs), apfl_value_move(&key), &out) != GET_ITEM_OK) { - return APFL_RESULT_ERR; // TODO: Describe error - } - - return stack_push(ctx, out) - ? APFL_RESULT_OK - : APFL_RESULT_ERR_FATAL; -} - -static enum apfl_result -evaluate_at(apfl_ctx ctx, struct apfl_expr_at *at) -{ - - TRY(evaluate(ctx, at->lhs)); - TRY(evaluate(ctx, at->rhs)); - - struct apfl_value rhs = stack_must_pop(ctx, -1); - struct apfl_value lhs = stack_must_pop(ctx, -1); - - struct apfl_value out; - if (apfl_value_get_item(ctx->allocator, apfl_value_move(&lhs), apfl_value_move(&rhs), &out) != GET_ITEM_OK) { - return APFL_RESULT_ERR; // TODO: Describe error - } - - return stack_push(ctx, out) - ? APFL_RESULT_OK - : APFL_RESULT_ERR_FATAL; -} - -static enum apfl_result -failing_match_result_to_apfl_result(enum match_result match_result) -{ - if (match_result == MATCH_FATAL_ERROR) { - return APFL_RESULT_ERR_FATAL; - } return APFL_RESULT_ERR; } -static enum match_result -evaluate_assignment_setup( - apfl_ctx ctx, - struct match_pattern *pattern, - struct apfl_expr_assignment *assignment -) { - enum match_result result = match_pattern_from_assignable( - ctx, - &assignment->lhs, - pattern - ); - - if (result != MATCH_OK) { - return result; - } - - if (!match_pattern_create_vars(ctx, pattern)) { - match_pattern_deinit(ctx, pattern); - return MATCH_FATAL_ERROR; - } - - return MATCH_OK; -} - -static enum match_result -evaluate_assignment_finish( - apfl_ctx ctx, - struct match_pattern *pattern, - struct apfl_value value -) { - enum match_result match_result = match_pattern_match( - ctx->allocator, - pattern, - apfl_value_move(&value) - ); - - if (match_result != MATCH_OK) { - return match_result; - } - - return match_pattern_apply(ctx, pattern); -} - - static enum apfl_result -evaluate_assignment(apfl_ctx ctx, struct apfl_expr_assignment *assignment) +evaluate_list(apfl_ctx ctx, struct instruction_list *ilist) { - struct match_pattern pattern; - enum match_result match_result = evaluate_assignment_setup( - ctx, - &pattern, - assignment - ); - - if (match_result != MATCH_OK) { - return failing_match_result_to_apfl_result(match_result); + ctx->execution_line = ilist->line; + size_t i = 0; + while (i < ilist->len) { + TRY(evaluate(ctx, &i, ilist)); } - - enum apfl_result result = evaluate(ctx, assignment->rhs); - if (result != APFL_RESULT_OK) { - match_pattern_deinit(ctx, &pattern); - return result; - } - - match_result = evaluate_assignment_finish( - ctx, - &pattern, - stack_must_get(ctx, -1) // stack_must_get instead of stack_must_pop, - // so the value is still on the stack on return. - ); - - match_pattern_deinit(ctx, &pattern); - if (match_result != MATCH_OK) { - stack_must_drop(ctx, -1); - return failing_match_result_to_apfl_result(match_result); - } - return APFL_RESULT_OK; } static enum apfl_result -evaluate_var(apfl_ctx ctx, apfl_refcounted_string varname) +eval_inner(apfl_ctx ctx, struct apfl_expr expr) { - variable var = ctx_get_var(ctx, varname); - if (var == NULL) { + struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line); + if (ilist == NULL) { + return APFL_RESULT_ERR_FATAL; + } + + if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) { + return APFL_RESULT_ERR_FATAL; + } + + struct apfl_error error; + if (!apfl_compile(&ctx->gc, expr, &error, ilist)) { return APFL_RESULT_ERR; } - bool ok = stack_push(ctx, apfl_value_incref(var->value)); - variable_unref(ctx, var); - return ok - ? APFL_RESULT_OK - : APFL_RESULT_ERR_FATAL; -} - -static enum apfl_result -evaluate(apfl_ctx ctx, struct apfl_expr *expr) -{ - switch (expr->type) { - case APFL_EXPR_CONSTANT: - return evaluate_constant(ctx, &expr->constant); - case APFL_EXPR_LIST: - return evaluate_list(ctx, &expr->list); - case APFL_EXPR_DICT: - return evaluate_dict(ctx, &expr->dict); - case APFL_EXPR_DOT: - return evaluate_dot(ctx, &expr->dot); - case APFL_EXPR_AT: - return evaluate_at(ctx, &expr->at); - case APFL_EXPR_ASSIGNMENT: - return evaluate_assignment(ctx, &expr->assignment); - case APFL_EXPR_VAR: - return evaluate_var(ctx, apfl_refcounted_string_incref(expr->var)); - case APFL_EXPR_BLANK: - return stack_push(ctx, (struct apfl_value) { - .type = VALUE_NIL, - }) - ? APFL_RESULT_OK - : APFL_RESULT_ERR_FATAL; - case APFL_EXPR_CALL: - case APFL_EXPR_SIMPLE_FUNC: - case APFL_EXPR_COMPLEX_FUNC: - break; // Not implemented yet - } - - return APFL_RESULT_ERR; + return evaluate_list(ctx, ilist); } enum apfl_result apfl_eval(apfl_ctx ctx, struct apfl_expr expr) { - enum apfl_result result = evaluate(ctx, &expr); - // TODO: expr might have been allocated with another allocator. The apfl_ctx // should probably also handle parsing and no longer accept // expressions directly. - apfl_expr_deinit(ctx->allocator, &expr); + + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + enum apfl_result result = eval_inner(ctx, expr); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); return result; } @@ -1411,11 +197,10 @@ void apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, FILE *f) { struct apfl_value value; - if (!stack_pop(ctx, &value, index)) { + if (!apfl_stack_pop(ctx, &value, index)) { fprintf(f, "apfl_debug_print_val: Invalid stack index %d\n", index); return; } apfl_value_print(value, f); - apfl_value_deinit(ctx->allocator, &value); } diff --git a/src/expr.c b/src/expr.c index 3844a25..68ec092 100644 --- a/src/expr.c +++ b/src/expr.c @@ -40,8 +40,7 @@ apfl_expr_deinit(struct apfl_allocator allocator, struct apfl_expr *expr) apfl_expr_const_deinit(allocator, &expr->constant); break; case APFL_EXPR_VAR: - apfl_refcounted_string_unref(allocator, expr->var); - expr->var = NULL; + apfl_string_deinit(allocator, &expr->var); break; case APFL_EXPR_BLANK: // nop @@ -100,8 +99,7 @@ apfl_expr_const_deinit(struct apfl_allocator allocator, struct apfl_expr_const * // nop break; case APFL_EXPR_CONST_STRING: - apfl_refcounted_string_unref(allocator, constant->string); - constant->string = NULL; + apfl_string_deinit(allocator, &constant->string); break; } } @@ -135,8 +133,7 @@ apfl_expr_param_deinit(struct apfl_allocator allocator, struct apfl_expr_param * { switch (param->type) { case APFL_EXPR_PARAM_VAR: - apfl_refcounted_string_unref(allocator, param->var); - param->var = NULL; + apfl_string_deinit(allocator, ¶m->var); break; case APFL_EXPR_PARAM_CONSTANT: apfl_expr_const_deinit(allocator, ¶m->constant); @@ -189,8 +186,7 @@ void apfl_expr_assignable_var_or_member_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_dot *dot) { DESTROY(allocator, dot->lhs, apfl_expr_assignable_var_or_member_deinit); - apfl_refcounted_string_unref(allocator, dot->rhs); - dot->rhs = NULL; + apfl_string_deinit(allocator, &dot->rhs); } void @@ -205,8 +201,7 @@ apfl_expr_assignable_var_or_member_deinit(struct apfl_allocator allocator, struc { switch (var_or_member->type) { case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR: - apfl_refcounted_string_unref(allocator, var_or_member->var); - var_or_member->var = NULL; + apfl_string_deinit(allocator, &var_or_member->var); break; case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT: apfl_expr_assignable_var_or_member_dot_deinit(allocator, &var_or_member->dot); @@ -250,8 +245,7 @@ void apfl_expr_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_dot *dot) { DESTROY(allocator, dot->lhs, apfl_expr_deinit); - apfl_refcounted_string_unref(allocator, dot->rhs); - dot->rhs = NULL; + apfl_string_deinit(allocator, &dot->rhs); } void @@ -295,7 +289,7 @@ apfl_expr_move(struct apfl_expr *in) out.constant = apfl_expr_const_move(&in->constant); break; case APFL_EXPR_VAR: - MOVEPTR(out.var, in->var); + out.var = apfl_string_move(&in->var); break; case APFL_EXPR_BLANK: // nop @@ -384,7 +378,7 @@ apfl_expr_const_move(struct apfl_expr_const *in) // nop break; case APFL_EXPR_CONST_STRING: - MOVEPTR(out.string,in->string); + out.string = apfl_string_move(&in->string); } return out; @@ -418,7 +412,7 @@ apfl_expr_param_move(struct apfl_expr_param *in) struct apfl_expr_param out = *in; switch (in->type) { case APFL_EXPR_PARAM_VAR: - MOVEPTR(out.var, in->var); + out.var = apfl_string_move(&in->var); break; case APFL_EXPR_PARAM_CONSTANT: out.constant = apfl_expr_const_move(&in->constant); @@ -459,11 +453,11 @@ apfl_expr_assignable_var_or_member_move(struct apfl_expr_assignable_var_or_membe struct apfl_expr_assignable_var_or_member out = *in; switch (in->type) { case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR: - MOVEPTR(out.var, in->var); + out.var = apfl_string_move(&in->var); break; case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT: MOVEPTR(out.dot.lhs, in->dot.lhs); - MOVEPTR(out.dot.rhs, in->dot.rhs); + out.dot.rhs = apfl_string_move(&in->dot.rhs); break; case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT: MOVEPTR(out.at.lhs, in->at.lhs); @@ -528,7 +522,7 @@ apfl_expr_dot_move(struct apfl_expr_dot *in) { struct apfl_expr_dot out; MOVEPTR(out.lhs, in->lhs); - MOVEPTR(out.rhs, in->rhs); + out.rhs = apfl_string_move(&in->rhs); return out; } diff --git a/src/gc.c b/src/gc.c new file mode 100644 index 0000000..d8804e8 --- /dev/null +++ b/src/gc.c @@ -0,0 +1,545 @@ +#include +#include + +#include "alloc.h" +#include "bytecode.h" +#include "context.h" +#include "gc.h" +#include "hashmap.h" +#include "resizable.h" +#include "value.h" + +#define DEBUG_GC 1 + +struct gc_object { + // Unlike most other tagged unions in apfl, the union is first here. + // This allows us to have pointers to the wrapped object that can be cast + // into gc_object pointers and vice versa. + union { + struct list_header list; + struct dict_header dict; + struct apfl_value var; + struct apfl_string string; + struct instruction_list instructions; + struct scope scope; + struct stack stack; + }; + enum gc_type type; + enum gc_status status; +}; + +#define GC_OBJECTS_PER_BLOCK 128 + +struct gc_block { + struct gc_object objects[GC_OBJECTS_PER_BLOCK]; + struct gc_block *next; +}; + +static void * +gc_allocator(void *opaque, void *oldptr, size_t oldsize, size_t newsize) +{ + struct gc *gc = opaque; + + void *out = ALLOCATOR_CALL(gc->base_allocator, oldptr, oldsize, newsize); + if (newsize != 0 && out == NULL) { + // We're out of memory! Try to get out of this situation by doing a full + // GC run. + apfl_gc_full(gc); + + // Hopefully we now have memory again. Try the allocation again. + out = ALLOCATOR_CALL(gc->base_allocator, oldptr, oldsize, newsize); + } + + if (newsize != 0 && out == NULL) { + return NULL; + } + + // TODO: incremental GC step + + return out; +} + +struct gc_object * +apfl_gc_object_from_ptr(void *ptr, enum gc_type type) +{ + struct gc_object *object = ptr; + assert(object->type == type); + return object; +} + +bool +apfl_gc_init(struct gc *gc, struct apfl_allocator allocator) +{ + gc->base_allocator = allocator; + gc->allocator = (struct apfl_allocator) { + .opaque = gc, + .alloc = gc_allocator, + }; + gc->block = NULL; + + gc->tmproots = (struct gc_tmproots) { + .roots = NULL, + .len = 0, + .cap = 0, + }; + gc->tmproot_for_adding = NULL; + + // TODO: It's a bit wasteful that we use a hashmap here. We are only + // ever interested in the keys. + return apfl_hashmap_init( + &gc->roots, + allocator, + (struct apfl_hashmap_callbacks) {.opaque = NULL}, + sizeof(struct gc_object *), + sizeof(char) + ); +} + +static struct gc_block * +new_block(struct gc *gc) +{ + struct gc_block *block = ALLOC_OBJ(gc->allocator, struct gc_block); + if (block == NULL) { + return NULL; + } + + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + block->objects[i] = (struct gc_object) { .status = GC_STATUS_FREE }; + } + block->next = NULL; + + return block; +} + +static struct gc_object * +new_object_inner(struct gc *gc) +{ + struct gc_block **cur = &gc->block; + + while (*cur != NULL) { + struct gc_block *block = *cur; + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + if (block->objects[i].status == GC_STATUS_FREE) { + return &block->objects[i]; + } + } + + cur = &block->next; + } + + struct gc_block *nb = new_block(gc); + if (nb == NULL) { + return NULL; + } + + *cur = nb; + + return &nb->objects[0]; +} + +static struct gc_object * +new_object(struct gc *gc, enum gc_type type) +{ + struct gc_object *object = new_object_inner(gc); + if (object == NULL) { + return NULL; + } + + assert(object->status == GC_STATUS_FREE); + object->status = GC_STATUS_WHITE; + object->type = type; + return object; +} + +#define IMPL_NEW(t, name, type, field) \ + t * \ + name(struct gc *gc) \ + { \ + struct gc_object *object = new_object(gc, type); \ + return object == NULL ? NULL : &object->field; \ + } + +IMPL_NEW(struct list_header, apfl_gc_new_list, GC_TYPE_LIST, list ) +IMPL_NEW(struct dict_header, apfl_gc_new_dict, GC_TYPE_DICT, dict ) +IMPL_NEW(struct apfl_value, apfl_gc_new_var, GC_TYPE_VAR, var ) +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 stack, apfl_gc_new_stack, GC_TYPE_STACK, stack ) + +bool +apfl_gc_root_add(struct gc *gc, struct gc_object *object) +{ + // Since setting the new root can trigger a garbage collection, we need to + // set the root as the tmproot_for_adding, so we'll treat it as a root and + // not free it. + assert(gc->tmproot_for_adding == NULL); + gc->tmproot_for_adding = object; + + char v = 0; + bool ok = apfl_hashmap_set(&gc->roots, &object, &v); + + gc->tmproot_for_adding = NULL; + + return ok; +} + +void +apfl_gc_root_remove(struct gc *gc, struct gc_object *object) +{ + apfl_hashmap_delete(&gc->roots, &object); +} + +size_t +apfl_gc_tmproots_begin(struct gc *gc) +{ + return gc->tmproots.len; +} + +void +apfl_gc_tmproots_restore(struct gc *gc, size_t newlen) +{ + assert(newlen <= gc->tmproots.len); + gc->tmproots.len = newlen; +} + +bool +apfl_gc_tmproot_add(struct gc *gc, struct gc_object *object) +{ + // Since appending the new tmproot can trigger a garbage collection, we need + // to set the tmproot as the tmproot_for_adding, so we'll treat it as a root + // and not free it. + assert(gc->tmproot_for_adding == NULL); + gc->tmproot_for_adding = object; + + bool ok = apfl_resizable_append( + gc->allocator, + sizeof(struct gc_object *), + (void **)&gc->tmproots.roots, + &gc->tmproots.len, + &gc->tmproots.cap, + &object, + 1 + ); + + gc->tmproot_for_adding = NULL; + + return ok; +} + +static void +color_object_grey(struct gc_object *object) +{ + object->status = object->status == GC_STATUS_BLACK ? GC_STATUS_BLACK : GC_STATUS_GREY; +} + +static void +visit_roots(struct gc *gc, gc_visitor visitor, void *opaque) +{ + HASHMAP_EACH(&gc->roots, cur) { + struct gc_object *obj; + apfl_hashmap_cursor_get_key(cur, &obj); + + visitor(opaque, obj); + } + + for (size_t i = 0; i < gc->tmproots.len; i++) { + visitor(opaque, gc->tmproots.roots[i]); + } + + if (gc->tmproot_for_adding != NULL) { + visitor(opaque, gc->tmproot_for_adding); + } +} + +static void +mark_roots_visitor(void *opaque, struct gc_object *root) +{ + (void)opaque; + color_object_grey(root); +} + +static void +mark_roots(struct gc *gc) +{ + visit_roots(gc, mark_roots_visitor, NULL); +} + +static void +visit_children(struct gc_object *object, gc_visitor cb, void *opaque) +{ + switch (object->type) { + case GC_TYPE_LIST: + apfl_gc_list_traverse(&object->list, cb, opaque); + return; + case GC_TYPE_DICT: + apfl_gc_dict_traverse(&object->dict, cb, opaque); + return; + case GC_TYPE_VAR: + apfl_gc_var_traverse(&object->var, cb, opaque); + return; + case GC_TYPE_SCOPE: + apfl_gc_scope_traverse(&object->scope, cb, opaque); + return; + case GC_TYPE_STRING: + // Intentionally left blank. Object doesn't reference other objects. + return; + case GC_TYPE_INSTRUCTIONS: + apfl_gc_instructions_traverse(&object->instructions, cb, opaque); + return; + case GC_TYPE_STACK: + apfl_gc_stack_traverse(&object->stack, cb, opaque); + return; + } + + assert(false); +} + +static void +trace_callback(void *opaque, struct gc_object *object) +{ + (void)opaque; + color_object_grey(object); +} + +static void +trace(struct gc_object *object) +{ + object->status = GC_STATUS_BLACK; + visit_children(object, trace_callback, NULL); +} + +static void +trace_while_having_grey(struct gc *gc) +{ + bool found_grey; + do { + found_grey = false; + for ( + struct gc_block *cur = gc->block; + cur != NULL; + cur = cur->next + ) { + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + struct gc_object *object = &cur->objects[i]; + if (object->status == GC_STATUS_GREY) { + trace(object); + found_grey = true; + } + } + } + } while (found_grey); +} + +static void +deinit_object(struct gc *gc, struct gc_object *object) +{ + switch (object->type) { + case GC_TYPE_LIST: + apfl_list_deinit(gc->allocator, &object->list); + return; + case GC_TYPE_DICT: + apfl_dict_deinit(&object->dict); + return; + case GC_TYPE_VAR: + return; + case GC_TYPE_STRING: + apfl_string_deinit(gc->allocator, &object->string); + return; + case GC_TYPE_INSTRUCTIONS: + apfl_instructions_deinit(gc->allocator, &object->instructions); + return; + case GC_TYPE_SCOPE: + apfl_scope_deinit(gc->allocator, &object->scope); + return; + case GC_TYPE_STACK: + apfl_stack_deinit(gc->allocator, &object->stack); + return; + } + + assert(false); +} + +static void +sweep(struct gc *gc) +{ + struct gc_block **cur = &gc->block; + while (*cur != NULL) { + struct gc_block *block = *cur; + + bool completely_free = true; + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + struct gc_object *object = &block->objects[i]; + + switch (object->status) { + case GC_STATUS_FREE: + break; + case GC_STATUS_WHITE: + deinit_object(gc, object); + object->status = GC_STATUS_FREE; + break; + case GC_STATUS_GREY: + assert(false /*Encountered grey object while sweeping*/); + break; + case GC_STATUS_BLACK: + object->status = GC_STATUS_WHITE; // Prepare for next run + completely_free = false; + break; + } + } + + if (completely_free) { + *cur = block->next; + FREE_OBJ(gc->allocator, block); + } else { + cur = &block->next; + } + } +} + +void +apfl_gc_full(struct gc *gc) +{ + mark_roots(gc); + apfl_gc_debug_dump_graph(gc, stderr); + trace_while_having_grey(gc); + apfl_gc_debug_dump_graph(gc, stderr); + sweep(gc); + apfl_gc_debug_dump_graph(gc, stderr); +} + +static const char * +dump_graph_bgcolor(enum gc_status status) +{ + switch (status) { + case GC_STATUS_BLACK: + return "black"; + case GC_STATUS_GREY: + return "grey"; + default: + return "white"; + } +} + +static const char * +dump_graph_fgcolor(enum gc_status status) +{ + switch (status) { + case GC_STATUS_BLACK: + return "white"; + default: + return "black"; + } +} + +static const char * +type_to_string(enum gc_type type) +{ + switch (type) { + case GC_TYPE_LIST: + return "list"; + case GC_TYPE_DICT: + return "dict"; + case GC_TYPE_VAR: + return "var"; + case GC_TYPE_STRING: + return "string"; + case GC_TYPE_INSTRUCTIONS: + return "instructions"; + case GC_TYPE_SCOPE: + return "scope"; + case GC_TYPE_STACK: + return "stack"; + } + + assert(false); + return "???"; +} + +static void +dump_graph_roots_visitor(void *opaque, struct gc_object *obj) +{ + FILE *out = opaque; + fprintf(out, "ROOTS -> obj_%p;\n", (void *)obj); +} + +struct dump_graph_visitor_data { + FILE *out; + struct gc_object *parent; +}; + +static void +dump_graph_visitor(void *opaque, struct gc_object *obj) +{ + struct dump_graph_visitor_data *data = opaque; + fprintf(data->out, "obj_%p -> obj_%p\n", (void *)data->parent, (void *)obj); +} + +void +apfl_gc_debug_dump_graph(struct gc *gc, FILE *out) +{ + fprintf(out, "digraph G {\n"); + visit_roots(gc, dump_graph_roots_visitor, out); + + for (struct gc_block *block = gc->block; block != NULL; block = block->next) { + int counts[4] = {0, 0, 0, 0}; + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + struct gc_object *obj = &block->objects[i]; + + counts[obj->status]++; + if (obj->status == GC_STATUS_FREE) { + continue; + } + + fprintf(out, "blk_%p -> obj_%p;\n", (void *)block, (void *)obj); + fprintf( + out, + "obj_%p [style=filled,fillcolor=%s,fontcolor=%s,label=\"Object %p\\ntype: %s\"];\n", + (void *)obj, + dump_graph_bgcolor(obj->status), + dump_graph_fgcolor(obj->status), + (void *)obj, + type_to_string(obj->type) + ); + + visit_children(obj, dump_graph_visitor, &(struct dump_graph_visitor_data) { + .out = out, + .parent = obj, + }); + } + + fprintf(out, "BLOCKS -> blk_%p;\n", (void *)block); + fprintf( + out, + "blk_%p [label=\"Block %p\\nfree %d, black %d, grey %d, white %d\"];\n", + (void *)block, + (void *)block, + counts[GC_STATUS_FREE], + counts[GC_STATUS_BLACK], + counts[GC_STATUS_GREY], + counts[GC_STATUS_WHITE] + ); + } + fprintf(out, "}\n"); +} + +void +apfl_gc_deinit(struct gc *gc) +{ + for (struct gc_block *block = gc->block; block != NULL; ) { + for (size_t i = 0; i < GC_OBJECTS_PER_BLOCK; i++) { + struct gc_object *object = &block->objects[i]; + if (object->status != GC_STATUS_FREE) { + deinit_object(gc, object); + } + } + + struct gc_block *next = block->next; + FREE_OBJ(gc->allocator, block); + block = next; + } + gc->block = NULL; + + FREE_LIST(gc->allocator, gc->tmproots.roots, gc->tmproots.cap); + apfl_hashmap_deinit(&gc->roots); +} diff --git a/src/gc.h b/src/gc.h new file mode 100644 index 0000000..fd59802 --- /dev/null +++ b/src/gc.h @@ -0,0 +1,86 @@ +#ifndef APFL_GC_H +#define APFL_GC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "apfl.h" + +#include "hashmap.h" + +struct gc_object; + +enum gc_status { + GC_STATUS_FREE, + GC_STATUS_WHITE, + GC_STATUS_GREY, + GC_STATUS_BLACK, +}; + +enum gc_type { + GC_TYPE_LIST, + GC_TYPE_DICT, + GC_TYPE_VAR, + GC_TYPE_STRING, + GC_TYPE_INSTRUCTIONS, + GC_TYPE_SCOPE, + GC_TYPE_STACK, +}; + +struct gc_tmproots { + struct gc_object **roots; + size_t len; + size_t cap; +}; + +struct gc { + struct apfl_allocator base_allocator; + struct apfl_allocator allocator; + + struct gc_block *block; + + struct apfl_hashmap roots; + struct gc_tmproots tmproots; + struct gc_object *tmproot_for_adding; +}; + +typedef void (*gc_visitor)(void *, struct gc_object *); + +#ifdef NDEBUG +# define GC_OBJECT_FROM(ptr, type) ((struct gc_object *)(ptr)) +#else +# define GC_OBJECT_FROM(ptr, type) apfl_gc_object_from_ptr((ptr), (type)) +#endif + +struct gc_object *apfl_gc_object_from_ptr(void *, enum gc_type); + +bool apfl_gc_init(struct gc *, struct apfl_allocator); +void apfl_gc_deinit(struct gc *); + +void apfl_gc_debug_dump_graph(struct gc *, FILE *); + +bool apfl_gc_root_add(struct gc *gc, struct gc_object *object); +void apfl_gc_root_remove(struct gc *gc, struct gc_object *object); + +size_t apfl_gc_tmproots_begin(struct gc *gc); +void apfl_gc_tmproots_restore(struct gc *gc, size_t); +bool apfl_gc_tmproot_add(struct gc *gc, struct gc_object *object); + +void apfl_gc_full(struct gc *gc); + +struct list_header* apfl_gc_new_list(struct gc *); +struct dict_header* apfl_gc_new_dict(struct gc *); +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 stack* apfl_gc_new_stack(struct gc *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/hashmap.c b/src/hashmap.c index ace01ec..3f2e222 100644 --- a/src/hashmap.c +++ b/src/hashmap.c @@ -94,25 +94,6 @@ copy_value(const struct apfl_hashmap map, void *dest, void *src) } } -static bool -copy_opaque(const struct apfl_hashmap map, void **dest, void *src) -{ - if (HAS_CALLBACK(map, copy_opaque)) { - return map.callbacks.copy_opaque(dest, src); - } else { - *dest = src; - return true; - } -} - -static void -deinit_opaque(const struct apfl_hashmap map) -{ - if (HAS_CALLBACK(map, deinit_opaque)) { - INVOKE_CALLBACK_NOARGS(map, deinit_opaque); - } -} - #define CAP_GROW 5 static_assert(CAP_GROW >= 1, "CAP_GROW must be at least 1"); @@ -418,8 +399,6 @@ apfl_hashmap_deinit(struct apfl_hashmap *map) FREE_LIST(map->allocator, map->buckets, map->nbuckets); map->buckets = NULL; } - - deinit_opaque(*map); } struct apfl_hashmap @@ -438,12 +417,8 @@ apfl_hashmap_copy(struct apfl_hashmap *dst, struct apfl_hashmap src) size_t valsize = src.valsize; struct apfl_hashmap_callbacks dst_callbacks = src.callbacks; - if (!copy_opaque(src, &dst_callbacks.opaque, src.callbacks.opaque)) { - return false; - } if (!hashmap_init(dst, src.allocator, dst_callbacks, src.nbuckets, keysize, valsize)) { - deinit_opaque(*dst); return false; } diff --git a/src/hashmap.h b/src/hashmap.h index 6b73f0b..3031ebd 100644 --- a/src/hashmap.h +++ b/src/hashmap.h @@ -44,12 +44,6 @@ struct apfl_hashmap_callbacks { // Copies a value. Returns true on success, false on failure. // If not provided, the bytes will be copied with memcpy. void (*copy_value) (void *opaque, void *dest, void *src); - - // Copies the opaque value. Returns true on success, false on failure. - bool (*copy_opaque) (void **dst, void *src); - - // Called on deinitialization of the hashmap, if provided. - void (*deinit_opaque)(void *opaque); }; struct apfl_hashmap { diff --git a/src/parser.c b/src/parser.c index e452eab..d032af2 100644 --- a/src/parser.c +++ b/src/parser.c @@ -49,7 +49,7 @@ enum fragment_type { struct fragment_dot { struct fragment *lhs; - apfl_refcounted_string rhs; + struct apfl_string rhs; }; struct fragment_lhs_rhs { @@ -67,7 +67,7 @@ struct fragment { union { struct fragment *expand; struct apfl_expr_const constant; - apfl_refcounted_string name; + struct apfl_string name; struct fragment_dot dot; struct fragment_lhs_rhs at; struct fragment_lhs_rhs predicate; @@ -136,11 +136,11 @@ fragment_deinit(struct apfl_allocator allocator, struct fragment *fragment) apfl_expr_const_deinit(allocator, &fragment->constant); break; case FRAG_NAME: - apfl_refcounted_string_unref_ptr(allocator, &fragment->name); + apfl_string_deinit(allocator, &fragment->name); break; case FRAG_DOT: DESTROY(allocator, fragment->dot.lhs, fragment_deinit); - apfl_refcounted_string_unref_ptr(allocator, &fragment->dot.rhs); + apfl_string_deinit(allocator, &fragment->dot.rhs); break; case FRAG_AT: deinit_fragment_lhs_rhs(allocator, &fragment->at); @@ -165,7 +165,7 @@ fragment_dot_move(struct fragment_dot *in) { struct fragment_dot out; MOVEPTR(out.lhs, in->lhs); - MOVEPTR(out.rhs, in->rhs); + out.rhs = apfl_string_move(&in->rhs); return out; } @@ -201,7 +201,7 @@ fragment_move(struct fragment *in) out.constant = apfl_expr_const_move(&in->constant); break; case FRAG_NAME: - MOVEPTR(out.name, in->name); + out.name = apfl_string_move(&in->name); break; case FRAG_DOT: out.dot = fragment_dot_move(&in->dot); @@ -629,7 +629,7 @@ fragment_to_expr_inner(apfl_parser_ptr p, struct fragment *fragment, struct apfl return true; case FRAG_NAME: out->type = APFL_EXPR_VAR; - out->var = apfl_refcounted_string_incref(fragment->name); + out->var = apfl_string_move(&fragment->name); out->position = fragment->position; return true; case FRAG_DOT: @@ -637,7 +637,7 @@ fragment_to_expr_inner(apfl_parser_ptr p, struct fragment *fragment, struct apfl if ((out->dot.lhs = fragment_to_expr_allocated(p, fragment_move(fragment->dot.lhs))) == NULL) { return false; } - out->dot.rhs = apfl_refcounted_string_incref(fragment->dot.rhs); + out->dot.rhs = apfl_string_move(&fragment->dot.rhs); out->position = fragment->position; return true; case FRAG_AT: @@ -1001,7 +1001,7 @@ parse_stringify(apfl_parser_ptr p, struct fragment *fragment, struct apfl_positi fragment->type = FRAG_CONSTANT; fragment->constant = (struct apfl_expr_const) { .type = APFL_EXPR_CONST_STRING, - .string = apfl_refcounted_string_incref(p->token.text), + .string = apfl_string_move(&p->token.text), }; fragment->position = position; return true; @@ -1070,7 +1070,7 @@ fragment_to_param_inner( return true; case FRAG_NAME: out->type = APFL_EXPR_PARAM_VAR; - out->var = apfl_refcounted_string_incref(fragment->name); + out->var = apfl_string_move(&fragment->name); return true; case FRAG_DOT: p->error = err_unexpected_token(APFL_TOK_DOT, fragment->position); @@ -1200,7 +1200,7 @@ static bool fragment_to_assignable_var_or_member( return false; case FRAG_NAME: out->type = APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR, - out->var = apfl_refcounted_string_incref(fragment->name); + out->var = apfl_string_move(&fragment->name); return true; case FRAG_DOT: lhs = ALLOC_OBJ(p->allocator, struct apfl_expr_assignable_var_or_member); @@ -1215,7 +1215,7 @@ static bool fragment_to_assignable_var_or_member( out->type = APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT; out->dot = (struct apfl_expr_assignable_var_or_member_dot) { .lhs = lhs, - .rhs = apfl_refcounted_string_incref(fragment->dot.rhs), + .rhs = apfl_string_move(&fragment->dot.rhs), }; return true; case FRAG_AT: @@ -2039,7 +2039,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par fragment->type = FRAG_BLANK; } else { fragment->type = FRAG_NAME; - fragment->name = apfl_refcounted_string_incref(p->token.text); + fragment->name = apfl_string_move(&p->token.text); } fragment->position = p->token.position; break; @@ -2047,7 +2047,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par fragment->type = FRAG_CONSTANT; fragment->constant = (struct apfl_expr_const) { .type = APFL_EXPR_CONST_STRING, - .string = apfl_refcounted_string_incref(p->token.text), + .string = apfl_string_move(&p->token.text), }; fragment->position = p->token.position; break; @@ -2084,7 +2084,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par fragment->type = FRAG_DOT; fragment->position = token_pos; MOVEPTR(fragment->dot.lhs, lhs); - fragment->dot.rhs = apfl_refcounted_string_incref(p->token.text); + fragment->dot.rhs = apfl_string_move(&p->token.text); break; case APFL_TOK_AT: diff --git a/src/parser_test.c b/src/parser_test.c index fda342e..90f7e58 100644 --- a/src/parser_test.c +++ b/src/parser_test.c @@ -114,11 +114,11 @@ expect_error_of_type(struct parser_test *pt, enum apfl_error_type want) } } -static apfl_refcounted_string +static struct apfl_string new_string(struct parser_test *pt, const char *in) { - apfl_refcounted_string out = apfl_string_copy_into_new_refcounted(pt->allocator, apfl_string_view_from(in)); - if (out == NULL) { + struct apfl_string out = apfl_string_blank(); + if (!apfl_string_copy(pt->allocator, &out, apfl_string_view_from(in))) { test_fatalf(pt->t, "Failed copying string in new_string"); } return out; diff --git a/src/resizable.c b/src/resizable.c index 0dd0340..01ba515 100644 --- a/src/resizable.c +++ b/src/resizable.c @@ -77,7 +77,51 @@ apfl_resizable_ensure_cap_for_more_elements( return apfl_resizable_ensure_cap(allocator, elem_size, mem, cap, len + more_elements); // TODO: What if len + more_elements overflows? } -bool apfl_resizable_splice( +bool +apfl_resizable_check_cut_args(size_t len, size_t cut_start, size_t cut_len) +{ + return !(cut_start > len || cut_start + cut_len > len); +} + +static void +move_elems_for_cut( + size_t elem_size, + void **mem, + size_t len, + size_t cut_start, + size_t cut_len, + size_t other_len +) { + size_t src_off = cut_start + cut_len; + size_t dst_off = cut_start + other_len; + + memmove( + ((char *)(*mem)) + (dst_off * elem_size), + ((char *)(*mem)) + (src_off * elem_size), + (len - cut_start - cut_len) * elem_size + ); +} + +bool +apfl_resizable_cut_without_resize( + size_t elem_size, + void **mem, + size_t *len, + size_t cut_start, + size_t cut_len +) { + if (!apfl_resizable_check_cut_args(*len, cut_start, cut_len)) { + return false; + } + + move_elems_for_cut(elem_size, mem, *len, cut_start, cut_len, 0); + *len -= cut_len; + + return true; +} + +bool +apfl_resizable_splice( struct apfl_allocator allocator, size_t elem_size, void **mem, @@ -88,7 +132,7 @@ bool apfl_resizable_splice( const void *other_mem, size_t other_len ) { - if (cut_start > *len || cut_start + cut_len > *len) { + if (!apfl_resizable_check_cut_args(*len, cut_start, cut_len)) { return false; } @@ -105,14 +149,7 @@ bool apfl_resizable_splice( } } - size_t src_off = cut_start + cut_len; - size_t dst_off = cut_start + other_len; - - memmove( - ((char *)(*mem)) + (dst_off * elem_size), - ((char *)(*mem)) + (src_off * elem_size), - (*len - cut_start - cut_len) * elem_size - ); + move_elems_for_cut(elem_size, mem, *len, cut_start, cut_len, other_len); if (other_len > 0 && other_mem != NULL) { memcpy( diff --git a/src/resizable.h b/src/resizable.h index ed3a6fa..323f83d 100644 --- a/src/resizable.h +++ b/src/resizable.h @@ -40,6 +40,16 @@ bool apfl_resizable_ensure_cap_for_more_elements( size_t more_elements ); +bool apfl_resizable_check_cut_args(size_t len, size_t cut_start, size_t cut_len); + +bool apfl_resizable_cut_without_resize( + size_t elem_size, + void **mem, + size_t *len, + size_t cut_start, + size_t cut_len +); + bool apfl_resizable_splice( struct apfl_allocator, size_t elem_size, diff --git a/src/strings.c b/src/strings.c index 2ff2a39..e634994 100644 --- a/src/strings.c +++ b/src/strings.c @@ -9,11 +9,6 @@ #include "internal.h" #include "resizable.h" -struct apfl_refcounted_string_data { - unsigned refcount; - struct apfl_string string; -}; - struct apfl_string_view apfl_string_view_from_view(struct apfl_string_view view) { @@ -158,75 +153,3 @@ apfl_string_builder_move_string(struct apfl_string_builder *builder) return str; } - -struct apfl_string_view -apfl_string_view_from_refcounted_string(apfl_refcounted_string rcstring) -{ - if (rcstring == NULL) { - return apfl_string_view_from(apfl_string_blank()); - } - - return apfl_string_view_from(rcstring->string); -} - -apfl_refcounted_string -apfl_string_copy_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string_view sv) -{ - struct apfl_string str = apfl_string_blank(); - if (!apfl_string_copy(allocator, &str, sv)) { - return NULL; - } - - apfl_refcounted_string rcstring = apfl_string_move_into_new_refcounted(allocator, &str); - if (rcstring == NULL) { - apfl_string_deinit(allocator, &str); - return NULL; - } - - return rcstring; -} - -apfl_refcounted_string -apfl_string_move_into_new_refcounted(struct apfl_allocator allocator, struct apfl_string *src) -{ - apfl_refcounted_string dst = ALLOC_OBJ( - allocator, - struct apfl_refcounted_string_data - ); - if (dst == NULL) { - // TODO: Or should we free src here? - return NULL; - } - - *dst = (struct apfl_refcounted_string_data) { - .refcount = 1, - .string = apfl_string_move(src), - }; - - return dst; -} - -apfl_refcounted_string -apfl_refcounted_string_incref(apfl_refcounted_string rcstring) -{ - if (rcstring != NULL) { - rcstring->refcount++; - } - return rcstring; -} - -void -apfl_refcounted_string_unref(struct apfl_allocator allocator, apfl_refcounted_string rcstring) -{ - if (rcstring != NULL && apfl_refcount_dec(&rcstring->refcount)) { - apfl_string_deinit(allocator, &rcstring->string); - FREE_OBJ(allocator, rcstring); - } -} - -void -apfl_refcounted_string_unref_ptr(struct apfl_allocator allocator, apfl_refcounted_string *rcstring_ptr) -{ - apfl_refcounted_string_unref(allocator, *rcstring_ptr); - *rcstring_ptr = NULL; -} diff --git a/src/token.c b/src/token.c index b3aee14..72e2aa9 100644 --- a/src/token.c +++ b/src/token.c @@ -32,7 +32,7 @@ void apfl_token_deinit(struct apfl_allocator allocator, struct apfl_token *token) { if (has_text_data(token->type)) { - apfl_refcounted_string_unref_ptr(allocator, &token->text); + apfl_string_deinit(allocator, &token->text); } } diff --git a/src/tokenizer.c b/src/tokenizer.c index 1d32b29..15fd6a5 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -260,21 +260,6 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need) } } -static apfl_refcounted_string -rcstring_from_string_builder(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *builder) -{ - struct apfl_string string = apfl_string_builder_move_string(builder); - apfl_refcounted_string rcstring = apfl_string_move_into_new_refcounted( - tokenizer->allocator, - &string - ); - if (rcstring == NULL) { - apfl_string_deinit(tokenizer->allocator, &string); - tokenizer->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); - } - return rcstring; -} - static enum apfl_parse_result comment(apfl_tokenizer_ptr tokenizer) { @@ -287,8 +272,6 @@ comment(apfl_tokenizer_ptr tokenizer) apfl_string_builder_init(tokenizer->allocator, &text); for (;;) { - apfl_refcounted_string rcstring; - last_pos = tokenizer->position; switch (read_byte(tokenizer, &byte, true)) { @@ -297,16 +280,11 @@ comment(apfl_tokenizer_ptr tokenizer) case RR_ERR: return APFL_PARSE_ERROR; case RR_EOF: - rcstring = rcstring_from_string_builder(tokenizer, &text); - if (rcstring == NULL) { - return APFL_PARSE_ERROR; - } - tokenizer->next_mode = NM_EOF; tokenizer->token = (struct apfl_token) { .type = APFL_TOK_COMMENT, .position = pos, - .text = rcstring, + .text = apfl_string_builder_move_string(&text), }; return APFL_PARSE_OK; } @@ -314,15 +292,10 @@ comment(apfl_tokenizer_ptr tokenizer) if (byte == '\n') { unread_byte(tokenizer, last_pos); - rcstring = rcstring_from_string_builder(tokenizer, &text); - if (rcstring == NULL) { - return APFL_PARSE_ERROR; - } - tokenizer->token = (struct apfl_token) { .type = APFL_TOK_COMMENT, .position = pos, - .text = rcstring, + .text = apfl_string_builder_move_string(&text), }; return APFL_PARSE_OK; } @@ -595,19 +568,12 @@ inner_string(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *text) return APFL_PARSE_ERROR; } - apfl_refcounted_string rcstring; - switch (byte) { case '"': - rcstring = rcstring_from_string_builder(tokenizer, text); - if (rcstring == NULL) { - return APFL_PARSE_ERROR; - } - tokenizer->token = (struct apfl_token) { .type = APFL_TOK_STRING, .position = pos, - .text = rcstring, + .text = apfl_string_builder_move_string(text), }; return APFL_PARSE_OK; case '\\': @@ -651,15 +617,10 @@ finalize_maybe_name( .position = pos, }; } else { - apfl_refcounted_string rcstring = rcstring_from_string_builder(tokenizer, text); - if (rcstring == NULL) { - return APFL_PARSE_ERROR; - } - tokenizer->token = (struct apfl_token) { .type = APFL_TOK_NAME, .position = pos, - .text = rcstring, + .text = apfl_string_builder_move_string(text), }; } diff --git a/src/value.c b/src/value.c index 04576fb..3bf1516 100644 --- a/src/value.c +++ b/src/value.c @@ -3,56 +3,18 @@ #include "apfl.h" #include "alloc.h" +#include "context.h" #include "internal.h" #include "resizable.h" #include "hashmap.h" #include "value.h" -struct apfl_list_data { - unsigned refcount; - struct apfl_value *items; - size_t len; - size_t cap; -}; - -struct apfl_dict_data { - unsigned refcount; - struct apfl_hashmap map; -}; - -static apfl_hash value_hash(const struct apfl_value); - -apfl_list -apfl_list_incref(apfl_list list) -{ - if (list == NULL) { - return NULL; - } - list->refcount++; - return list; -} - size_t -apfl_list_len(apfl_list list) +apfl_list_len(struct list_header *list) { return list->len; } -void -apfl_list_unref(struct apfl_allocator allocator, apfl_list list) -{ - if (list == NULL || !apfl_refcount_dec(&list->refcount)) { - return; - } - - for (size_t i = 0; i < list->len; i++) { - apfl_value_deinit(allocator, &list->items[i]); - } - - FREE_LIST(allocator, list->items, list->cap); - FREE_OBJ(allocator, list); -} - static bool dict_keys_eq(void *opaque, const void *a, const void *b) { @@ -64,100 +26,51 @@ static apfl_hash dict_calc_hash(void *opaque, const void *key) { (void)opaque; - return value_hash(*(const struct apfl_value *)key); -} - -static void -dict_destroy_key_or_value(void *opaque, void *kv) -{ - struct apfl_allocator *allocator = opaque; - apfl_value_deinit(*allocator, kv); -} - -static void -dict_copy_key_or_value(void *opaque, void *_dest, void *_src) -{ - (void)opaque; - - struct apfl_value *dest = _dest; - struct apfl_value *src = _src; - - *dest = apfl_value_incref(*src); + return apfl_value_hash(*(const struct apfl_value *)key); } static bool -dict_copy_opaque(void **_dst, void *src) +dict_init_hashmap(struct apfl_allocator allocator, struct apfl_hashmap *map) { - struct apfl_allocator *allocator = src; - struct apfl_allocator *dst; - if ((dst = ALLOC_OBJ(*allocator, struct apfl_allocator)) == NULL) { - return false; - } - *dst = *allocator; - *_dst = dst; - return true; -} - -static void -dict_deinit_opaque(void *opaque) -{ - struct apfl_allocator *allocator = opaque; - FREE_OBJ(*allocator, allocator); -} - -static bool -init_hashmap_for_dict(struct apfl_allocator allocator, struct apfl_hashmap *map) -{ - struct apfl_allocator *allocator_ptr = ALLOC_OBJ(allocator, struct apfl_allocator); - if (allocator_ptr == NULL) { - return false; - } - *allocator_ptr = allocator; - bool ok = apfl_hashmap_init( map, allocator, (struct apfl_hashmap_callbacks) { - .opaque = allocator_ptr, .keys_eq = dict_keys_eq, .calc_hash = dict_calc_hash, - .destroy_key = dict_destroy_key_or_value, - .destroy_value = dict_destroy_key_or_value, - .copy_key = dict_copy_key_or_value, - .copy_value = dict_copy_key_or_value, - .copy_opaque = dict_copy_opaque, - .deinit_opaque = dict_deinit_opaque, }, sizeof(struct apfl_value), sizeof(struct apfl_value) ); - if (!ok) { - FREE_OBJ(allocator, allocator_ptr); - } - return ok; } -apfl_dict -apfl_dict_incref(apfl_dict dict) +struct dict_header * +apfl_dict_new(struct gc *gc) { - if (dict == NULL) { + struct apfl_hashmap map; + + if (!dict_init_hashmap(gc->allocator, &map)) { return NULL; } - dict->refcount++; + + struct dict_header *dict = apfl_gc_new_dict(gc); + if (dict == NULL) { + apfl_hashmap_deinit(&map); + return NULL; + } + *dict = (struct dict_header) { + .map = map, + .copy_on_write = false, + }; return dict; } void -apfl_dict_unref(struct apfl_allocator allocator, apfl_dict dict) +apfl_dict_deinit(struct dict_header *header) { - if (dict == NULL || !apfl_refcount_dec(&dict->refcount)) { - return; - } - - apfl_hashmap_deinit(&dict->map); - FREE_OBJ(allocator, dict); + apfl_hashmap_deinit(&header->map); } static void @@ -177,7 +90,7 @@ print(unsigned indent, FILE *out, struct apfl_value value, bool skip_first_inden apfl_print_indented(first_indent, out, "%f", value.number); return; case VALUE_STRING: - sv = apfl_string_view_from(value.string); + sv = apfl_string_view_from(*value.string); apfl_print_indented(first_indent, out, "\"" APFL_STR_FMT "\"", APFL_STR_FMT_ARGS(sv)); return; case VALUE_LIST: @@ -227,31 +140,6 @@ apfl_value_move(struct apfl_value *src) return out; } -struct apfl_value -apfl_value_incref(struct apfl_value value) -{ - switch (value.type) { - case VALUE_NIL: - case VALUE_BOOLEAN: - case VALUE_NUMBER: - // Nothing to do - return value; - case VALUE_STRING: - value.string = apfl_refcounted_string_incref(value.string); - return value; - case VALUE_LIST: - value.list = apfl_list_incref(value.list); - return value; - case VALUE_DICT: - value.dict = apfl_dict_incref(value.dict); - return value; - } - - assert(false); - - return value; -} - void apfl_value_print(struct apfl_value value, FILE *out) { @@ -260,7 +148,7 @@ apfl_value_print(struct apfl_value value, FILE *out) } static bool -list_eq(apfl_list a, apfl_list b) +list_eq(struct list_header *a, struct list_header *b) { if (a == b) { return true; @@ -325,7 +213,7 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b) case VALUE_NUMBER: return a.number == b.number; case VALUE_STRING: - return apfl_string_eq(a.string, b.string); + return a.string == b.string || apfl_string_eq(*a.string, *b.string); case VALUE_LIST: return list_eq(a.list, b.list); case VALUE_DICT: @@ -336,294 +224,188 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b) return false; } -struct apfl_editable_list_data { - struct apfl_allocator allocator; - struct apfl_value *items; - size_t len; - size_t cap; -}; - -apfl_editable_list -apfl_editable_list_new(struct apfl_allocator allocator) +struct list_header * +apfl_list_new(struct gc *gc, size_t initial_cap) { - apfl_editable_list elist = ALLOC_OBJ(allocator, struct apfl_editable_list_data); - if (elist == NULL) { - return NULL; - } - - elist->allocator = allocator; - elist->items = NULL; - elist->len = 0; - elist->cap = 0; - - return elist; -} - -static bool -editable_list_append_values(apfl_editable_list elist, struct apfl_value *values, size_t len) -{ - if (!apfl_resizable_ensure_cap_for_more_elements( - elist->allocator, - sizeof(struct apfl_value), - (void **)&elist->items, - elist->len, - &elist->cap, - len - )) { - return false; - } - - for (size_t i = 0; i < len; i++) { - elist->items[elist->len] = apfl_value_incref(values[i]); - elist->len++; - } - - return true; -} - -bool -apfl_editable_list_append(apfl_editable_list elist, struct apfl_value value) -{ - bool ok = editable_list_append_values(elist, &value, 1); - apfl_value_deinit(elist->allocator, &value); - return ok; -} - -bool -apfl_editable_list_append_list(apfl_editable_list elist, apfl_list list) -{ - bool ok = editable_list_append_values(elist, list->items, list->len); - apfl_list_unref(elist->allocator, list); - return ok; -} - -void -apfl_editable_list_destroy(apfl_editable_list elist) -{ - if (elist == NULL) { - return; - } - - if (elist->items != NULL) { - for (size_t i = 0; i < elist->len; i++) { - apfl_value_deinit(elist->allocator, &elist->items[i]); - } - FREE_LIST(elist->allocator, elist->items, elist->cap); - } - - FREE_OBJ(elist->allocator, elist); -} - -apfl_list -apfl_editable_list_finalize(apfl_editable_list elist) -{ - apfl_list list = ALLOC_OBJ(elist->allocator, struct apfl_list_data); - if (list == NULL) { - apfl_editable_list_destroy(elist); - return NULL; - } - - list->refcount = 1; - list->items = elist->items; // TODO: Maybe shrink memory with realloc? - list->len = elist->len; - list->cap = elist->cap; - - FREE_OBJ(elist->allocator, elist); - - return list; -} - -struct apfl_editable_dict_data { - struct apfl_allocator allocator; - struct apfl_hashmap map; -}; - -apfl_editable_dict -apfl_editable_dict_new(struct apfl_allocator allocator) -{ - apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data); - if (ed == NULL) { - return NULL; - } - - ed->allocator = allocator; - if (!init_hashmap_for_dict(allocator, &ed->map)) { - FREE_OBJ(allocator, ed); - return NULL; - } - - return ed; -} - -apfl_editable_dict -apfl_editable_dict_new_from_dict(struct apfl_allocator allocator, apfl_dict dict) -{ - if (dict->refcount == 0) { - return NULL; - } - - apfl_editable_dict ed = ALLOC_OBJ(allocator, struct apfl_editable_dict_data); - if (ed == NULL) { - apfl_dict_unref(allocator, dict); - return NULL; - } - - ed->allocator = allocator; - if (dict->refcount == 1) { - // We're the only one having a reference to the dict. We can directly use it's hashmap! - ed->map = apfl_hashmap_move(&dict->map); - } else { - // There are other places referencing the dict, we need to create a shallow copy first. - if (!apfl_hashmap_copy(&ed->map, dict->map)) { - FREE_OBJ(allocator, ed); - apfl_dict_unref(allocator, dict); + struct apfl_value *items = NULL; + if (initial_cap > 0) { + items = ALLOC_LIST(gc->allocator, struct apfl_value, initial_cap); + if (items == NULL) { return NULL; } } - apfl_dict_unref(allocator, dict); - - return ed; -} - -bool -apfl_editable_dict_get( - apfl_editable_dict ed, - struct apfl_value key, - struct apfl_value *out -) { - bool ok = apfl_hashmap_get(&ed->map, &key, out); - apfl_value_deinit(ed->allocator, &key); - return ok; -} - -bool -apfl_editable_dict_set(apfl_editable_dict ed, struct apfl_value key, struct apfl_value value) -{ - if (ed == NULL) { - return false; + struct list_header *list = apfl_gc_new_list(gc); + if (list == NULL) { + FREE_LIST(gc->allocator, items, initial_cap); + return NULL; } - bool ok = apfl_hashmap_set(&ed->map, &key, &value); - apfl_value_deinit(ed->allocator, &key); - apfl_value_deinit(ed->allocator, &value); - return ok; + *list = (struct list_header) { + .items = items, + .len = 0, + .cap = initial_cap, + .copy_on_write = false, + }; + return list; } void -apfl_editable_dict_delete(apfl_editable_dict ed, struct apfl_value key) +apfl_list_deinit(struct apfl_allocator allocator, struct list_header *list) { - if (ed == NULL) { - return; - } - - apfl_hashmap_delete(&ed->map, &key); - apfl_value_deinit(ed->allocator, &key); + FREE_LIST(allocator, list->items, list->cap); } -apfl_dict -apfl_editable_dict_finalize(apfl_editable_dict ed) +bool +apfl_list_splice( + struct gc *gc, + struct list_header **dst_ptr, + size_t cut_start, + size_t cut_len, + const struct apfl_value *other, + size_t other_len +) { + struct list_header *dst = *dst_ptr; + + if (!apfl_resizable_check_cut_args(dst->len, cut_start, cut_len)) { + return false; + } + + if (!dst->copy_on_write) { + return apfl_resizable_splice( + gc->allocator, + sizeof(struct apfl_value), + (void **)&dst->items, + &dst->len, + &dst->cap, + cut_start, + cut_len, + other, + other_len + ); + } + + size_t len = dst->len - cut_len + other_len; + struct apfl_value *items = ALLOC_LIST(gc->allocator, struct apfl_value, len); + if (len > 0 && items == NULL) { + return false; + } + + struct list_header *new_list = apfl_gc_new_list(gc); + if (new_list == NULL) { + FREE_LIST(gc->allocator, items, len); + return false; + } + + // Note that we set the COW flag here, as the values now live in two places. + for (size_t i = 0; i < cut_start; i++) { + items[i] = apfl_value_set_cow_flag(dst->items[i]); + } + for (size_t i = 0; i < other_len; i++) { + items[cut_start + i] = apfl_value_set_cow_flag(other[i]); + } + for (size_t i = cut_start + cut_len; i < dst->len; i++) { + items[other_len + i - cut_len] = apfl_value_set_cow_flag(dst->items[i]); + } + + *new_list = (struct list_header) { + .items = items, + .len = len, + .cap = len, + .copy_on_write = false, + }; + + *dst_ptr = new_list; + return true; +} + +/* Returns a dictionary for editing. Will create a copy, if neccessary. + * *_dict must be known to the garbage collector! + */ +static struct dict_header * +dict_get_for_editing(struct gc *gc, struct dict_header **_dict) { - if (ed == NULL) { + struct dict_header *dict = *_dict; + if (!dict->copy_on_write) { + return dict; + } + + struct dict_header copy = {.copy_on_write = false}; + if (!apfl_hashmap_copy(©.map, dict->map)) { return NULL; } - apfl_dict dict = ALLOC_OBJ(ed->allocator, struct apfl_dict_data); + // Set the COW flags of all keys and values in the copy. + HASHMAP_EACH(©.map, cur) { + struct apfl_value *item; + + apfl_hashmap_cursor_peek_key(cur, (void **)&item); + *item = apfl_value_set_cow_flag(*item); + + apfl_hashmap_cursor_peek_value(cur, (void **)&item); + *item = apfl_value_set_cow_flag(*item); + } + + dict = apfl_gc_new_dict(gc); if (dict == NULL) { - apfl_editable_dict_destroy(ed); + apfl_hashmap_deinit(©.map); return NULL; } - dict->refcount = 1; - dict->map = apfl_hashmap_move(&ed->map); - - FREE_OBJ(ed->allocator, ed); + *dict = copy; + *_dict = dict; return dict; } -void -apfl_editable_dict_destroy(apfl_editable_dict ed) -{ - if (ed == NULL) { - return; - } - - apfl_hashmap_deinit(&ed->map); - FREE_OBJ(ed->allocator, ed); -} - -void -apfl_value_deinit(struct apfl_allocator allocator, struct apfl_value *value) -{ - switch (value->type) { - case VALUE_NIL: - case VALUE_BOOLEAN: - case VALUE_NUMBER: - goto ok; - case VALUE_STRING: - apfl_refcounted_string_unref(allocator, value->string); - value->string = NULL; - goto ok; - case VALUE_LIST: - apfl_list_unref(allocator, value->list); - value->list = NULL; - goto ok; - case VALUE_DICT: - apfl_dict_unref(allocator, value->dict); - value->dict = NULL; - goto ok; - } - - assert(false); - -ok: - value->type = VALUE_NIL; -} - +/* Set a key-value pair in a raw dictionary. + * *_dict, k and v must all be known to the garbage collector! + */ bool -apfl_list_get_item(struct apfl_allocator allocator, apfl_list list, size_t index, struct apfl_value *out) -{ - if (index >= list->len) { - apfl_list_unref(allocator, list); +apfl_dict_set_raw( + struct gc *gc, + struct dict_header **_dict, + struct apfl_value k, + struct apfl_value v +) { + struct dict_header *dict = dict_get_for_editing(gc, _dict); + if (dict == NULL) { return false; } - *out = apfl_value_incref(list->items[index]); - apfl_list_unref(allocator, list); + return apfl_hashmap_set(&dict->map, &k, &v); +} + +static bool +list_get_item(struct list_header *list, size_t index, struct apfl_value *out) +{ + if (index >= list->len) { + return false; + } + + *out = list->items[index]; return true; } -bool -apfl_dict_get_item(struct apfl_allocator allocator, apfl_dict dict, struct apfl_value key, struct apfl_value *out) -{ - bool ok = apfl_hashmap_get(&dict->map, &key, out); - apfl_dict_unref(allocator, dict); - apfl_value_deinit(allocator, &key); - return ok; -} - static enum get_item_result -get_item(struct apfl_allocator allocator, /*borrowed*/ struct apfl_value container, /*borrowed*/ struct apfl_value key, struct apfl_value *out) +value_get_item_inner(struct apfl_value container, struct apfl_value key, struct apfl_value *out) { if (container.type == VALUE_LIST) { if (key.type != VALUE_NUMBER) { return GET_ITEM_WRONG_KEY_TYPE; } - return apfl_list_get_item( - allocator, - apfl_list_incref(container.list), + return list_get_item( + container.list, (size_t)key.number, out ) ? GET_ITEM_OK : GET_ITEM_KEY_DOESNT_EXIST; } else if (container.type == VALUE_DICT) { - return apfl_dict_get_item( - allocator, - apfl_dict_incref(container.dict), - apfl_value_incref(key), + return apfl_hashmap_get( + &container.dict->map, + &key, out ) ? GET_ITEM_OK @@ -634,16 +416,34 @@ get_item(struct apfl_allocator allocator, /*borrowed*/ struct apfl_value contain } enum get_item_result -apfl_value_get_item(struct apfl_allocator allocator, struct apfl_value container, struct apfl_value key, struct apfl_value *out) +apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out) { - enum get_item_result result = get_item(allocator, container, key, out); - apfl_value_deinit(allocator, &container); - apfl_value_deinit(allocator, &key); + enum get_item_result result = value_get_item_inner(container, key, out); + if (result == GET_ITEM_OK) { + *out = apfl_value_set_cow_flag(*out); + } return result; } -static apfl_hash -value_hash(const struct apfl_value value) +struct apfl_value +apfl_value_set_cow_flag(struct apfl_value value) +{ + switch (value.type) { + case VALUE_LIST: + value.list->copy_on_write = true; + break; + case VALUE_DICT: + value.dict->copy_on_write = true; + break; + default: + break; + } + + return value; +} + +apfl_hash +apfl_value_hash(const struct apfl_value value) { apfl_hash hash = apfl_hash_fnv1a(&value.type, sizeof(enum value_type)); @@ -659,12 +459,12 @@ value_hash(const struct apfl_value value) hash = apfl_hash_fnv1a_add(&value.number, sizeof(apfl_number), hash); goto ok; case VALUE_STRING: - sv = apfl_string_view_from(value.string); + sv = apfl_string_view_from(*value.string); hash = apfl_hash_fnv1a_add(sv.bytes, sv.len, hash); goto ok; case VALUE_LIST: for (size_t i = 0; i < value.list->len; i++) { - apfl_hash item_hash = value_hash(value.list->items[i]); + apfl_hash item_hash = apfl_value_hash(value.list->items[i]); hash = apfl_hash_fnv1a_add(&item_hash, sizeof(apfl_hash), hash); } goto ok; @@ -680,3 +480,56 @@ value_hash(const struct apfl_value value) ok: return hash; } + +struct gc_object * +apfl_value_get_gc_object(struct apfl_value value) +{ + switch (value.type) { + case VALUE_NIL: + case VALUE_BOOLEAN: + case VALUE_NUMBER: + return NULL; + case VALUE_STRING: + return GC_OBJECT_FROM(value.string, GC_TYPE_STRING); + case VALUE_LIST: + return GC_OBJECT_FROM(value.list, GC_TYPE_LIST); + case VALUE_DICT: + return GC_OBJECT_FROM(value.dict, GC_TYPE_DICT); + } + + assert(false); + return NULL; +} + +static void +call_visitor_for_value(gc_visitor cb, void *opaque, struct apfl_value value) +{ + struct gc_object *child = apfl_value_get_gc_object(value); + if (child != NULL) { + cb(opaque, child); + } +} + +void +apfl_gc_list_traverse(struct list_header *list, gc_visitor cb, void *opaque) +{ + for (size_t i = 0; i < list->len; i++) { + call_visitor_for_value(cb, opaque, list->items[i]); + } +} + +void +apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque) +{ + HASHMAP_EACH(&dict->map, cur) { + struct apfl_value *k; + struct apfl_value *v; + + apfl_hashmap_cursor_peek_key(cur, (void **)&k); + apfl_hashmap_cursor_peek_value(cur, (void **)&v); + + call_visitor_for_value(cb, opaque, *k); + call_visitor_for_value(cb, opaque, *v); + } +} + diff --git a/src/value.h b/src/value.h index 745f5de..c097639 100644 --- a/src/value.h +++ b/src/value.h @@ -5,11 +5,16 @@ extern "C" { #endif +#include #include #include #include "apfl.h" +#include "gc.h" + +#include "hashmap.h" + enum value_type { VALUE_NIL, VALUE_BOOLEAN, @@ -20,29 +25,39 @@ enum value_type { // TODO: functions/closures }; -struct apfl_list_data; -typedef struct apfl_list_data *apfl_list; +struct list_header { + struct apfl_value *items; + size_t len; + size_t cap; + bool copy_on_write; +}; -struct apfl_dict_data; -typedef struct apfl_dict_data *apfl_dict; +struct dict_header { + struct apfl_hashmap map; + bool copy_on_write; +}; + +typedef struct dict_header *apfl_dict; struct apfl_value { enum value_type type; union { bool boolean; apfl_number number; - apfl_refcounted_string string; - apfl_list list; - apfl_dict dict; + struct apfl_string *string; + struct list_header *list; + struct dict_header *dict; }; }; +enum apfl_value_type apfl_value_type_to_abstract_type(enum value_type); + +#define VALUE_IS_A(v, t) (apfl_value_type_to_abstract_type((v).type) == (t)) bool apfl_value_eq(const struct apfl_value, const struct apfl_value); struct apfl_value apfl_value_move(struct apfl_value *src); -struct apfl_value apfl_value_incref(struct apfl_value); void apfl_value_print(struct apfl_value, FILE *); -void apfl_value_deinit(struct apfl_allocator, struct apfl_value *); +apfl_hash apfl_value_hash(const struct apfl_value); enum get_item_result { GET_ITEM_OK, @@ -51,46 +66,40 @@ enum get_item_result { GET_ITEM_WRONG_KEY_TYPE, }; -enum get_item_result apfl_value_get_item(struct apfl_allocator allocator, struct apfl_value container, struct apfl_value key, struct apfl_value *out); +enum get_item_result apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out); -apfl_list apfl_list_incref(apfl_list); -size_t apfl_list_len(apfl_list); -bool apfl_list_get_item(struct apfl_allocator, apfl_list, size_t index, struct apfl_value *out); -void apfl_list_unref(struct apfl_allocator, apfl_list); +// Set the Copy On Write flag of a value (if it's a value with such a flag). +// Returns the same value again. +struct apfl_value apfl_value_set_cow_flag(struct apfl_value); -apfl_dict apfl_dict_incref(apfl_dict); -bool apfl_dict_get_item(struct apfl_allocator, apfl_dict, struct apfl_value key, struct apfl_value *out); -void apfl_dict_unref(struct apfl_allocator, apfl_dict); +struct list_header *apfl_list_new(struct gc *, size_t initial_cap); -struct apfl_editable_list_data; -typedef struct apfl_editable_list_data *apfl_editable_list; +size_t apfl_list_len(struct list_header *); +void apfl_list_deinit(struct apfl_allocator, struct list_header *); -apfl_editable_list apfl_editable_list_new(struct apfl_allocator); -bool apfl_editable_list_append(apfl_editable_list, struct apfl_value); -bool apfl_editable_list_append_list(apfl_editable_list, apfl_list); -void apfl_editable_list_destroy(apfl_editable_list); +bool +apfl_list_splice( + struct gc *gc, + struct list_header **dst_ptr, + size_t cut_start, + size_t cut_len, + const struct apfl_value *other, + size_t other_len +); -/* Finalize the list and return a non-editable list. - * This also destroys the editable list object, so it's no longer safe to use it. - * Returns NULL on failure - */ -apfl_list apfl_editable_list_finalize(apfl_editable_list); +struct dict_header *apfl_dict_new(struct gc *); +bool apfl_dict_set_raw( + struct gc *gc, + struct dict_header **_dict, + struct apfl_value k, + struct apfl_value v +); +void apfl_dict_deinit(struct dict_header *); -struct apfl_editable_dict_data; -typedef struct apfl_editable_dict_data *apfl_editable_dict; - -apfl_editable_dict apfl_editable_dict_new(struct apfl_allocator); -apfl_editable_dict apfl_editable_dict_new_from_dict(struct apfl_allocator, apfl_dict); -bool apfl_editable_dict_get(apfl_editable_dict, struct apfl_value key, struct apfl_value *out); -bool apfl_editable_dict_set(apfl_editable_dict, struct apfl_value key, struct apfl_value value); -void apfl_editable_dict_delete(apfl_editable_dict, struct apfl_value key); -void apfl_editable_dict_destroy(apfl_editable_dict); - -/* Finalize the dictionary and return a non-editable dictionary. - * This also destroys the editable dictionary object, so it's no longer safe to use it. - * Returns NULL on failure - */ -apfl_dict apfl_editable_dict_finalize(apfl_editable_dict); +// 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 *); #ifdef __cplusplus }