From 20c2880f4c52477930dbd3f5c7c9632e595adf7c Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sat, 19 Nov 2022 22:06:23 +0100 Subject: [PATCH] Implement assigning into dictionaries It's now possible to assign to a key of a dictionary and even to a nested key path. This patch changes the way matchers work a bit: First, a function call stack frame now has a stack of matchers that are manipulateable instead of a single matcher. Second, the matcher is now in charge of setting the matched values to the variables (previously the caller of the matcher needed to extract the matched values and assign them itself). This change simplifies code generation, especially for chained assignments and dictionary key paths. This removes the last usage of APFL_ERR_NOT_IMPLEMENTED :) --- src/CMakeLists.txt | 2 +- src/apfl.h | 3 +- src/bytecode.c | 118 ++-- src/bytecode.h | 20 +- src/compile.c | 396 ++++++------ src/context.c | 233 +++++-- src/context.h | 49 +- src/error.c | 7 - src/eval.c | 587 ++++++++++++------ .../dictionary-assignments.at | 78 +++ src/functional-tests/shadowing.at | 22 + src/gc.c | 4 +- src/matcher.c | 83 --- src/matcher.h | 29 - src/scope.c | 5 +- src/value.c | 10 +- src/value.h | 1 + 17 files changed, 1034 insertions(+), 613 deletions(-) create mode 100644 src/functional-tests/dictionary-assignments.at delete mode 100644 src/matcher.c delete mode 100644 src/matcher.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 87ccecd..3abf973 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,6 @@ add_library(apfl gc.c hashmap.c globals.c - matcher.c messages.c parser.c position.c @@ -67,6 +66,7 @@ functionaltest("if") functionaltest("while") functionaltest("eq") functionaltest("chained-assignments") +functionaltest("dictionary-assignments") install(TARGETS apfl DESTINATION lib) install(TARGETS apfl-bin DESTINATION bin) diff --git a/src/apfl.h b/src/apfl.h index d6f4251..3c03a7d 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -211,7 +211,6 @@ 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); @@ -682,6 +681,8 @@ void apfl_dict_create(apfl_ctx); void apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v); // Get a value from a container (list or dict) and push it on the stack. container and k will be dropped. void apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k); +// Get a value from a container (list or dict) and push it on the stack, if it exists. container and k will be dropped. +bool apfl_get_member_if_exists(apfl_ctx, apfl_stackidx container, apfl_stackidx k); // Get a value from a list and push it on the stack. The list will stay on the stack. void apfl_get_list_member_by_index(apfl_ctx, apfl_stackidx list, size_t index_in_list); // Get the length of a string/list/dict. The value stays on the stack, diff --git a/src/bytecode.c b/src/bytecode.c index 580bf11..f4eb172 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -54,9 +54,9 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi case INSN_GET_MEMBER: case INSN_NEXT_LINE: case INSN_DROP: + case INSN_DUP: case INSN_CALL: case INSN_MATCHER_MUST_MATCH: - case INSN_MATCHER_DROP: break; case INSN_NUMBER: case INSN_LIST: @@ -80,14 +80,41 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi GET_ARGUMENT(ilist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS)); break; - case INSN_MATCHER_LOAD: + case INSN_MATCHER_PUSH: GET_ARGUMENT(ilist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.matcher, GC_TYPE_MATCHER_INSTRUCTIONS)); break; - case INSN_VAR_SET_FROM_MATCHER: - case INSN_VAR_SET_LOCAL_FROM_MATCHER: - GET_ARGUMENT(ilist, i, arg); + } + } +} + +void +apfl_gc_matcher_instructions_traverse(struct matcher_instruction_list *milist, gc_visitor cb, void *opaque) +{ + union matcher_instruction_or_arg arg; + + for (size_t i = 0; i < milist->len; i++) { + switch (milist->instructions[i].instruction) { + case MATCHER_IGNORE: + case MATCHER_ENTER_LIST: + case MATCHER_LEAVE_LIST: + case MATCHER_CONTINUE_FROM_END: + case MATCHER_REMAINDING: + break; + case MATCHER_CHECK_CONST: // with index as values index + case MATCHER_CHECK_PRED: // with index as values index + i++; + break; + case MATCHER_CAPTURE_TO_VAR: // with name + case MATCHER_CAPTURE_TO_VAR_LOCAL: // with name + GET_ARGUMENT(milist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); + break; + case MATCHER_CAPTURE_TO_VAR_WITH_PATH: // with name, index and len + case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: // with name, index and len + GET_ARGUMENT(milist, i, arg); + cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); + i++; i++; break; } @@ -140,24 +167,20 @@ apfl_instruction_to_string(enum instruction insn) return "INSN_GET_BY_INDEX_KEEP"; case INSN_DROP: return "INSN_DROP"; + case INSN_DUP: + return "INSN_DUP"; case INSN_CALL: return "INSN_CALL"; case INSN_FUNC: return "INSN_FUNC"; case INSN_FUNC_ADD_SUBFUNC: return "INSN_FUNC_ADD_SUBFUNC"; - case INSN_MATCHER_LOAD: - return "INSN_MATCHER_LOAD"; + case INSN_MATCHER_PUSH: + return "INSN_MATCHER_PUSH"; case INSN_MATCHER_SET_VAL: return "INSN_MATCHER_SET_VAL"; case INSN_MATCHER_MUST_MATCH: return "INSN_MATCHER_MUST_MATCH"; - case INSN_MATCHER_DROP: - return "INSN_MATCHER_DROP"; - case INSN_VAR_SET_FROM_MATCHER: - return "INSN_VAR_SET_FROM_MATCHER"; - case INSN_VAR_SET_LOCAL_FROM_MATCHER: - return "INSN_VAR_SET_LOCAL_FROM_MATCHER"; } return "??"; @@ -169,8 +192,14 @@ apfl_matcher_instruction_to_string(enum matcher_instruction insn) switch (insn) { case MATCHER_IGNORE: return "MATCHER_IGNORE"; - case MATCHER_CAPTURE: - return "MATCHER_CAPTURE"; + case MATCHER_CAPTURE_TO_VAR: + return "MATCHER_CAPTURE_TO_VAR"; + case MATCHER_CAPTURE_TO_VAR_LOCAL: + return "MATCHER_CAPTURE_TO_VAR_LOCAL"; + case MATCHER_CAPTURE_TO_VAR_WITH_PATH: + return "MATCHER_CAPTURE_TO_VAR_WITH_PATH"; + case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: + return "MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH"; case MATCHER_CHECK_CONST: return "MATCHER_CHECK_CONST"; case MATCHER_CHECK_PRED: @@ -211,10 +240,21 @@ apfl_matcher_instructions_deinit(struct apfl_allocator allocator, struct matcher FREE_LIST(allocator, milist->instructions, milist->cap); } +#define GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg) \ + do { \ + if (i >= ilist->len) { \ + FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted")); \ + return false; \ + } \ + arg = ilist->instructions[++i]; \ + } while (0) + bool apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct matcher_instruction_list *milist) { for (size_t i = 0; i < milist->len; i++) { + union matcher_instruction_or_arg arg; + FMT_TRY(apfl_format_put_indent(w, indent)); FMT_TRY(apfl_format_put_string(w, apfl_matcher_instruction_to_string(milist->instructions[i].instruction))); @@ -225,15 +265,29 @@ apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct case MATCHER_CONTINUE_FROM_END: case MATCHER_REMAINDING: break; - case MATCHER_CAPTURE: + case MATCHER_CAPTURE_TO_VAR: + case MATCHER_CAPTURE_TO_VAR_LOCAL: + GET_ARGUMENT_FOR_DUMP(w, milist, i, arg); + FMT_TRY(apfl_format_put_string(w, " ")); + FMT_TRY(apfl_format_put_string(w, *arg.string)); + break; + case MATCHER_CAPTURE_TO_VAR_WITH_PATH: // with string, index and len + case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: // with string, index and len + GET_ARGUMENT_FOR_DUMP(w, milist, i, arg); + FMT_TRY(apfl_format_put_string(w, " ")); + FMT_TRY(apfl_format_put_string(w, *arg.string)); + GET_ARGUMENT_FOR_DUMP(w, milist, i, arg); + FMT_TRY(apfl_format_put_string(w, ", ")); + FMT_TRY(apfl_format_put_int(w, (int)arg.index)); + GET_ARGUMENT_FOR_DUMP(w, milist, i, arg); + FMT_TRY(apfl_format_put_string(w, ", ")); + FMT_TRY(apfl_format_put_int(w, (int)arg.len)); + break; case MATCHER_CHECK_CONST: case MATCHER_CHECK_PRED: - if (i >= milist->len) { - FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted")); - return false; - } + GET_ARGUMENT_FOR_DUMP(w, milist, i, arg); FMT_TRY(apfl_format_put_string(w, " ")); - FMT_TRY(apfl_format_put_int(w, (int)milist->instructions[++i].index)); + FMT_TRY(apfl_format_put_int(w, (int)arg.index)); break; } @@ -243,15 +297,6 @@ apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct return true; } -#define GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg) \ - do { \ - if (i >= ilist->len) { \ - FMT_TRY(apfl_format_put_string(w, "Bytecode corrupted")); \ - return false; \ - } \ - arg = ilist->instructions[++i]; \ - } while (0) - bool apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruction_list *ilist) { @@ -272,9 +317,9 @@ apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruct case INSN_GET_MEMBER: case INSN_NEXT_LINE: case INSN_DROP: + case INSN_DUP: case INSN_CALL: case INSN_MATCHER_MUST_MATCH: - case INSN_MATCHER_DROP: break; case INSN_NUMBER: GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); @@ -312,22 +357,13 @@ apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruct FMT_TRY(apfl_format_put_indent(w, indent)); FMT_TRY(apfl_format_put_string(w, "}")); break; - case INSN_MATCHER_LOAD: + case INSN_MATCHER_PUSH: GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); FMT_TRY(apfl_format_put_string(w, " milist{\n")); FMT_TRY(apfl_bytecode_dump_matcher(indent+1, w, arg.matcher)); FMT_TRY(apfl_format_put_indent(w, indent)); FMT_TRY(apfl_format_put_string(w, "}")); break; - case INSN_VAR_SET_FROM_MATCHER: - case INSN_VAR_SET_LOCAL_FROM_MATCHER: - GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); - FMT_TRY(apfl_format_put_string(w, " ")); - FMT_TRY(apfl_format_put_string(w, *arg.string)); - FMT_TRY(apfl_format_put_string(w, ", ")); - GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); - FMT_TRY(apfl_format_put_int(w, (int)arg.index)); - break; } FMT_TRY(apfl_format_put_char(w, '\n')); diff --git a/src/bytecode.h b/src/bytecode.h index e4a1184..de2f828 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -11,7 +11,10 @@ extern "C" { enum matcher_instruction { MATCHER_IGNORE, - MATCHER_CAPTURE, // with index as capture index + MATCHER_CAPTURE_TO_VAR, // with name + MATCHER_CAPTURE_TO_VAR_LOCAL, // with name + MATCHER_CAPTURE_TO_VAR_WITH_PATH, // with name, index and len + MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH, // with name, index and len MATCHER_CHECK_CONST, // with index as values index MATCHER_CHECK_PRED, // with index as values index MATCHER_ENTER_LIST, @@ -23,14 +26,16 @@ enum matcher_instruction { union matcher_instruction_or_arg { enum matcher_instruction instruction; size_t index; + size_t len; + struct apfl_string *string; }; struct matcher_instruction_list { union matcher_instruction_or_arg *instructions; size_t len; size_t cap; - size_t capture_count; size_t value_count; + size_t capture_count; }; enum instruction { @@ -55,15 +60,13 @@ enum instruction { INSN_NEXT_LINE, // ( -- ) INSN_SET_LINE, // ( -- ), arg: count (new line number) INSN_DROP, // ( value -- ) + INSN_DUP, // ( value -- value value) INSN_CALL, // ( func list -- value ) INSN_FUNC, // ( -- func ), arg: count - INSN_FUNC_ADD_SUBFUNC, // ( func -- func' ), arg: body - INSN_MATCHER_LOAD, // ( -- ), arg: matcher + INSN_FUNC_ADD_SUBFUNC, // ( func -- func' ), arg: body; pops a matcher from the matcher stack + INSN_MATCHER_PUSH, // ( -- ), arg: matcher; pushes a matcher onto the matcher stack INSN_MATCHER_SET_VAL, // ( val -- ), arg: index - INSN_MATCHER_MUST_MATCH, // ( val -- ) - INSN_MATCHER_DROP, // ( -- ) - INSN_VAR_SET_FROM_MATCHER, // ( -- ), args: string, index - INSN_VAR_SET_LOCAL_FROM_MATCHER, // ( -- ), args: string, index + INSN_MATCHER_MUST_MATCH, // ( val -- ); pops a matcher from the matcher stack }; union instruction_or_arg { @@ -91,6 +94,7 @@ 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 *); +void apfl_gc_matcher_instructions_traverse(struct matcher_instruction_list *, gc_visitor, void *); struct matcher_instruction_list *apfl_matcher_instructions_new(struct gc *); void apfl_matcher_instructions_deinit(struct apfl_allocator, struct matcher_instruction_list *); diff --git a/src/compile.c b/src/compile.c index 1f1c42b..e01782b 100644 --- a/src/compile.c +++ b/src/compile.c @@ -112,28 +112,37 @@ milist_ensure_cap(struct compiler *compiler, struct matcher_instruction_list *mi )); } +#define MILIST_PUT(milist, member, value) \ + assert(milist->cap > 0); \ + assert(milist->len < milist->cap); \ + \ + milist->instructions[milist->len] = (union matcher_instruction_or_arg) { \ + .member = value, \ + }; \ + milist->len++; + static void milist_put_insn(struct matcher_instruction_list *milist, enum matcher_instruction instruction) { - assert(milist->cap > 0); - assert(milist->len < milist->cap); - - milist->instructions[milist->len] = (union matcher_instruction_or_arg) { - .instruction = instruction, - }; - milist->len++; + MILIST_PUT(milist, instruction, instruction) } static void milist_put_index(struct matcher_instruction_list *milist, size_t index) { - assert(milist->cap > 0); - assert(milist->len < milist->cap); + MILIST_PUT(milist, index, index) +} - milist->instructions[milist->len] = (union matcher_instruction_or_arg) { - .index = index, - }; - milist->len++; +static void +milist_put_len(struct matcher_instruction_list *milist, size_t len) +{ + MILIST_PUT(milist, len, len) +} + +static void +milist_put_string(struct matcher_instruction_list *milist, struct apfl_string *string) +{ + MILIST_PUT(milist, string, string) } static bool @@ -320,36 +329,88 @@ tmp_milist(struct compiler *compiler) return milist; } -struct compile_matchable_ilists { - struct instruction_list *prelude; - struct instruction_list *newvars; - struct instruction_list *setvars; - struct matcher_instruction_list *matcher; +struct compile_matchable_args { + struct instruction_list *ilist; + struct matcher_instruction_list *milist; + bool local; }; static bool compile_assignable( struct compiler *compiler, - bool local, struct apfl_position position, struct apfl_expr_assignable *assignable, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ); +static enum matcher_instruction +matcher_capture_to_var(bool local) +{ + return local ? MATCHER_CAPTURE_TO_VAR_LOCAL : MATCHER_CAPTURE_TO_VAR; +} + +static enum matcher_instruction +matcher_capture_to_var_with_path(bool local) +{ + return local ? MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH : MATCHER_CAPTURE_TO_VAR_WITH_PATH; +} + +static enum instruction +insn_var_new(bool local) +{ + return local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW; +} + +static bool +compile_assignable_var_path( + struct compiler *compiler, + struct apfl_expr_assignable_var_or_member *var_or_member, + struct compile_matchable_args args, + size_t *path_len, + struct apfl_string **varname +) { + size_t index; + struct apfl_string *dot_rhs = NULL; + + switch (var_or_member->type) { + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR: + TRY(ilist_ensure_cap(compiler, args.ilist, 2)); + TRY(string_move_into_new(compiler, varname, &var_or_member->var)); + ilist_put_insn(args.ilist, insn_var_new(args.local)); + ilist_put_string(args.ilist, *varname); + return true; + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT: + TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname)); + index = args.milist->value_count++; + ++*path_len; + TRY(ilist_ensure_cap(compiler, args.ilist, 4)); + TRY(string_move_into_new(compiler, &dot_rhs, &var_or_member->dot.rhs)); + ilist_put_insn(args.ilist, INSN_STRING); + ilist_put_string(args.ilist, dot_rhs); + ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); + ilist_put_insn(args.ilist, index); + return true; + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT: + TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname)); + index = args.milist->value_count++; + ++*path_len; + TRY(compile_expr(compiler, var_or_member->at.rhs, args.ilist)); + TRY(ilist_ensure_cap(compiler, args.ilist, 2)); + ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); + ilist_put_index(args.ilist, index); + return true; + } + + assert(false); + return false; +} + static bool compile_assignable_var_or_member( struct compiler *compiler, - bool local, struct apfl_expr_assignable_var_or_member *var_or_member, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ) { - size_t index = ilists.matcher->capture_count++; - - if (var_or_member->type != APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR) { - compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); // TODO: Implement me - return false; - } - /* TODO: What should happen, if we try to match the same variable twice? * * Right now, the second capture will overwrite the first one, but it would @@ -362,22 +423,27 @@ compile_assignable_var_or_member( * } */ - TRY(milist_ensure_cap(compiler, ilists.matcher, 2)); - milist_put_insn(ilists.matcher, MATCHER_CAPTURE); - milist_put_index(ilists.matcher, index); - - TRY(ilist_ensure_cap(compiler, ilists.newvars, 2)); + args.milist->capture_count++; + size_t path_start = args.milist->value_count; + size_t path_len = 0; struct apfl_string *varname = NULL; - TRY(string_move_into_new(compiler, &varname, &var_or_member->var)); - ilist_put_insn(ilists.newvars, local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW); - ilist_put_string(ilists.newvars, varname); + TRY(compile_assignable_var_path(compiler, var_or_member, args, &path_len, &varname)); - TRY(ilist_ensure_cap(compiler, ilists.setvars, 3)); - ilist_put_insn(ilists.setvars, local ? INSN_VAR_SET_LOCAL_FROM_MATCHER : INSN_VAR_SET_FROM_MATCHER); - ilist_put_string(ilists.setvars, varname); - ilist_put_index(ilists.setvars, index); + assert(varname != NULL /* if compile_assignable_var_path succeeded, it should have set varname */); + + if (path_len == 0) { + TRY(milist_ensure_cap(compiler, args.milist, 2)); + milist_put_insn(args.milist, matcher_capture_to_var(args.local)); + milist_put_string(args.milist, varname); + } else { + TRY(milist_ensure_cap(compiler, args.milist, 4)); + milist_put_insn(args.milist, matcher_capture_to_var_with_path(args.local)); + milist_put_string(args.milist, varname); + milist_put_index(args.milist, path_start); + milist_put_len(args.milist, path_len); + } return true; } @@ -386,45 +452,44 @@ static bool compile_matchable_constant( struct compiler *compiler, struct apfl_expr_const *constant, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ) { - size_t index = ilists.matcher->value_count++; + size_t index = args.milist->value_count++; - TRY(compile_constant(compiler, constant, ilists.prelude)); - TRY(ilist_ensure_cap(compiler, ilists.prelude, 2)); - ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL); - ilist_put_index(ilists.prelude, index); + TRY(compile_constant(compiler, constant, args.ilist)); + TRY(ilist_ensure_cap(compiler, args.ilist, 2)); + ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); + ilist_put_index(args.ilist, index); - TRY(milist_ensure_cap(compiler, ilists.matcher, 3)); - milist_put_insn(ilists.matcher, MATCHER_CHECK_CONST); - milist_put_index(ilists.matcher, index); - milist_put_insn(ilists.matcher, MATCHER_IGNORE); + TRY(milist_ensure_cap(compiler, args.milist, 3)); + milist_put_insn(args.milist, MATCHER_CHECK_CONST); + milist_put_index(args.milist, index); + milist_put_insn(args.milist, MATCHER_IGNORE); return true; } -#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \ - static bool \ - name( \ - struct compiler *compiler, \ - bool local, \ - struct apfl_position position, \ - predtype *predicate, \ - struct compile_matchable_ilists ilists \ - ) { \ - size_t index = ilists.matcher->value_count++; \ - \ - TRY(compile_expr(compiler, predicate->rhs, ilists.prelude)); \ - TRY(ilist_ensure_cap(compiler, ilists.prelude, 2)); \ - ilist_put_insn(ilists.prelude, INSN_MATCHER_SET_VAL); \ - ilist_put_index(ilists.prelude, index); \ - \ - TRY(milist_ensure_cap(compiler, ilists.matcher, 2)); \ - milist_put_insn(ilists.matcher, MATCHER_CHECK_PRED); \ - milist_put_index(ilists.matcher, index); \ - \ - return compile_matchable(compiler, local, position, predicate->lhs, ilists); \ - } \ +#define DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE(name, compile_matchable, predtype) \ + static bool \ + name( \ + struct compiler *compiler, \ + struct apfl_position position, \ + predtype *predicate, \ + struct compile_matchable_args args \ + ) { \ + size_t index = args.milist->value_count++; \ + \ + TRY(compile_expr(compiler, predicate->rhs, args.ilist)); \ + TRY(ilist_ensure_cap(compiler, args.ilist, 2)); \ + ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL); \ + ilist_put_index(args.ilist, index); \ + \ + TRY(milist_ensure_cap(compiler, args.milist, 2)); \ + milist_put_insn(args.milist, MATCHER_CHECK_PRED); \ + milist_put_index(args.milist, index); \ + \ + return compile_matchable(compiler, position, predicate->lhs, args); \ + } \ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( compile_assignable_predicate, @@ -436,18 +501,17 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( static bool \ name##_expand( \ struct compiler *compiler, \ - bool local, \ struct apfl_position position, \ listtype *list, \ - struct compile_matchable_ilists ilists, \ + struct compile_matchable_args args, \ size_t expand_at \ ) { \ assert(expand_at < list->len); \ listitemtype *expand_item = &list->listmemb[expand_at]; \ assert(expand_item->expand); \ \ - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ - milist_put_insn(ilists.matcher, MATCHER_CONTINUE_FROM_END); \ + TRY(milist_ensure_cap(compiler, args.milist, 1)); \ + milist_put_insn(args.milist, MATCHER_CONTINUE_FROM_END); \ \ for (size_t i = list->len; i-- > expand_at+1; ) { \ listitemtype *item = &list->listmemb[i]; \ @@ -460,13 +524,13 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( return false; \ } \ \ - TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \ + TRY(compile_matchable(compiler, position, &item->listitemmemb, args)); \ } \ \ - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ - milist_put_insn(ilists.matcher, MATCHER_REMAINDING); \ + TRY(milist_ensure_cap(compiler, args.milist, 1)); \ + milist_put_insn(args.milist, MATCHER_REMAINDING); \ \ - TRY(compile_matchable(compiler, local, position, &expand_item->listitemmemb, ilists)); \ + TRY(compile_matchable(compiler, position, &expand_item->listitemmemb, args)); \ \ return true; \ } \ @@ -474,13 +538,12 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( static bool \ name( \ struct compiler *compiler, \ - bool local, \ struct apfl_position position, \ listtype *list, \ - struct compile_matchable_ilists ilists \ + struct compile_matchable_args args \ ) { \ - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ - milist_put_insn(ilists.matcher, MATCHER_ENTER_LIST); \ + TRY(milist_ensure_cap(compiler, args.milist, 1)); \ + milist_put_insn(args.milist, MATCHER_ENTER_LIST); \ \ for (size_t i = 0; i < list->len; i++) { \ listitemtype *item = &list->listmemb[i]; \ @@ -488,19 +551,18 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( if (item->expand) { \ return name##_expand( \ compiler, \ - local, \ position, \ list, \ - ilists, \ + args, \ i \ ); \ } \ \ - TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \ + TRY(compile_matchable(compiler, position, &item->listitemmemb, args)); \ } \ \ - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ - milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); \ + TRY(milist_ensure_cap(compiler, args.milist, 1)); \ + milist_put_insn(args.milist, MATCHER_LEAVE_LIST); \ return true; \ } \ @@ -514,32 +576,31 @@ DEF_COMPILE_ABSTRACT_MATCHABLE_LIST( ) static bool -compile_matchable_blank(struct compiler *compiler, struct compile_matchable_ilists ilists) +compile_matchable_blank(struct compiler *compiler, struct matcher_instruction_list *milist) { - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_IGNORE); + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, MATCHER_IGNORE); return true; } static bool compile_assignable( struct compiler *compiler, - bool local, struct apfl_position position, struct apfl_expr_assignable *assignable, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ) { switch (assignable->type) { case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER: - return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, ilists); + return compile_assignable_var_or_member(compiler, &assignable->var_or_member, args); case APFL_EXPR_ASSIGNABLE_CONSTANT: - return compile_matchable_constant(compiler, &assignable->constant, ilists); + return compile_matchable_constant(compiler, &assignable->constant, args); case APFL_EXPR_ASSIGNABLE_PREDICATE: - return compile_assignable_predicate(compiler, local, position, &assignable->predicate, ilists); + return compile_assignable_predicate(compiler, position, &assignable->predicate, args); case APFL_EXPR_ASSIGNABLE_LIST: - return compile_assignable_list(compiler, local, position, &assignable->list, ilists); + return compile_assignable_list(compiler, position, &assignable->list, args); case APFL_EXPR_ASSIGNABLE_BLANK: - return compile_matchable_blank(compiler, ilists); + return compile_matchable_blank(compiler, args.milist); } assert(false); @@ -547,16 +608,28 @@ compile_assignable( } static bool -concat_ilists(struct compiler *compiler, struct instruction_list *out, const struct instruction_list *in) -{ - TRY(ilist_ensure_cap(compiler, out, in->len)); - memcpy( - out->instructions + out->len, - in->instructions, - sizeof(union instruction_or_arg) * in->len - ); - out->len += in->len; - return true; +compile_assignment_lhs( + struct compiler *compiler, + struct apfl_expr_assignable *assignable, + bool local, + struct apfl_position position, + struct instruction_list *ilist +) { + // By ensuring the capacity early, we can avoid tmprooting the milist, since + // we can immediately append it to the already GC-rooted ilist. + TRY(ilist_ensure_cap(compiler, ilist, 2)); + + struct matcher_instruction_list *milist = apfl_matcher_instructions_new(compiler->gc); + MALLOC_FAIL_IF_NULL(compiler, milist); + + ilist_put_insn(ilist, INSN_MATCHER_PUSH); + ilist_put_matcher(ilist, milist); + + return compile_assignable(compiler, position, assignable, (struct compile_matchable_args) { + .ilist = ilist, + .milist = milist, + .local = local, + }); } static bool @@ -566,43 +639,19 @@ compile_complex_assignment( struct apfl_position position, struct instruction_list *ilist ) { - // TODO: This is not entirely correct: By evaluating the RHS first, it might - // affect variables used in the matcher. This is only really important when - // predicates are used, which are not yet implemented: - // - // x = {true} - // foo?x = ({x={false}}) - // - // This should succeed: The predicate x should be loaded before the - // evaluation of the rhs occurs. With our current implementation it will - // fail, though. + TRY(compile_assignment_lhs( + compiler, + &assignment->lhs, + assignment->local, + position, + ilist + )); + TRY(compile_expr(compiler, assignment->rhs, ilist)); TRY(ilist_ensure_cap(compiler, ilist, 2)); - - struct compile_matchable_ilists ilists = { - .prelude = ilist, - }; - - MALLOC_FAIL_IF_NULL(compiler, (ilists.matcher = tmp_milist(compiler))); - MALLOC_FAIL_IF_NULL(compiler, (ilists.newvars = tmp_ilist(compiler, position.line))); - MALLOC_FAIL_IF_NULL(compiler, (ilists.setvars = tmp_ilist(compiler, position.line))); - - ilist_put_insn(ilist, INSN_MATCHER_LOAD); - ilist_put_matcher(ilist, ilists.matcher); - - TRY(compile_assignable(compiler, assignment->local, position, &assignment->lhs, ilists)); - - TRY(concat_ilists(compiler, ilist, ilists.newvars)); - - TRY(ilist_ensure_cap(compiler, ilist, 1)); + ilist_put_insn(ilist, INSN_DUP); ilist_put_insn(ilist, INSN_MATCHER_MUST_MATCH); - - TRY(concat_ilists(compiler, ilist, ilists.setvars)); - - TRY(ilist_ensure_cap(compiler, ilist, 1)); - ilist_put_insn(ilist, INSN_MATCHER_DROP); - return true; } @@ -647,6 +696,8 @@ compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct inst static bool compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist) { + // TODO: This leaves the results of all expressions on the value stack. + // The runtime will clean this up, but we van be less wasteful here. for (size_t i = 0; i < body->len; i++) { TRY(compile_expr(compiler, &body->items[i], ilist)); } @@ -666,16 +717,12 @@ compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func TRY(milist_ensure_cap(compiler, milist, 1)); milist_put_insn(milist, MATCHER_IGNORE); // Ignore all arguments - // Drop the matcher, we ignore arguments in simple functions - TRY(ilist_ensure_cap(compiler, body_ilist, 1)); - ilist_put_insn(body_ilist, INSN_MATCHER_DROP); - TRY(compile_body(compiler, func, body_ilist)); TRY(ilist_ensure_cap(compiler, ilist, 6)); ilist_put_insn(ilist, INSN_FUNC); ilist_put_count(ilist, 1); - ilist_put_insn(ilist, INSN_MATCHER_LOAD); + ilist_put_insn(ilist, INSN_MATCHER_PUSH); ilist_put_matcher(ilist, milist); ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC); ilist_put_body(ilist, body_ilist); @@ -686,10 +733,9 @@ compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func static bool compile_param( struct compiler *compiler, - bool local, struct apfl_position position, struct apfl_expr_param *param, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ); DEF_COMPILE_ABSTRACT_MATCHABLE_LIST( @@ -711,9 +757,9 @@ static bool compile_param_var( struct compiler *compiler, struct apfl_string *var, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ) { - size_t index = ilists.matcher->capture_count++; + args.milist->capture_count++; /* TODO: What should happen, if we try to match the same variable twice? * @@ -727,18 +773,13 @@ compile_param_var( * } */ - TRY(milist_ensure_cap(compiler, ilists.matcher, 2)); - milist_put_insn(ilists.matcher, MATCHER_CAPTURE); - milist_put_index(ilists.matcher, index); - - TRY(ilist_ensure_cap(compiler, ilists.setvars, 3)); + TRY(milist_ensure_cap(compiler, args.milist, 2)); struct apfl_string *varname = NULL; TRY(string_move_into_new(compiler, &varname, var)); - ilist_put_insn(ilists.setvars, INSN_VAR_SET_LOCAL_FROM_MATCHER); - ilist_put_string(ilists.setvars, varname); - ilist_put_index(ilists.setvars, index); + milist_put_insn(args.milist, MATCHER_CAPTURE_TO_VAR_LOCAL); + milist_put_string(args.milist, varname); return true; } @@ -746,24 +787,21 @@ compile_param_var( static bool compile_param( struct compiler *compiler, - bool local, struct apfl_position position, struct apfl_expr_param *param, - struct compile_matchable_ilists ilists + struct compile_matchable_args args ) { - (void)local; - switch (param->type) { case APFL_EXPR_PARAM_VAR: - return compile_param_var(compiler, ¶m->var, ilists); + return compile_param_var(compiler, ¶m->var, args); case APFL_EXPR_PARAM_CONSTANT: - return compile_matchable_constant(compiler, ¶m->constant, ilists); + return compile_matchable_constant(compiler, ¶m->constant, args); case APFL_EXPR_PARAM_LIST: - return compile_param_list(compiler, true, position, ¶m->list, ilists); + return compile_param_list(compiler, position, ¶m->list, args); case APFL_EXPR_PARAM_PREDICATE: - return compile_param_predicate(compiler, true, position, ¶m->predicate, ilists); + return compile_param_predicate(compiler, position, ¶m->predicate, args); case APFL_EXPR_PARAM_BLANK: - return compile_matchable_blank(compiler, ilists); + return compile_matchable_blank(compiler, args.milist); } assert(false); @@ -785,23 +823,19 @@ compile_subfunc(struct compiler *compiler, struct apfl_expr_subfunc *subfunc, st struct instruction_list *body_ilist = NULL; MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, position.line))); - struct matcher_instruction_list *matcher = NULL; - MALLOC_FAIL_IF_NULL(compiler, (matcher = tmp_milist(compiler))); + struct matcher_instruction_list *milist = NULL; + MALLOC_FAIL_IF_NULL(compiler, (milist = tmp_milist(compiler))); TRY(ilist_ensure_cap(compiler, ilist, 2)); - ilist_put_insn(ilist, INSN_MATCHER_LOAD); - ilist_put_matcher(ilist, matcher); + ilist_put_insn(ilist, INSN_MATCHER_PUSH); + ilist_put_matcher(ilist, milist); - TRY(compile_param_list(compiler, true, position, &subfunc->params, (struct compile_matchable_ilists) { - .prelude = ilist, - .newvars = NULL, // We don't use newvars when compiling parameters - .setvars = body_ilist, - .matcher = matcher, + TRY(compile_param_list(compiler, position, &subfunc->params, (struct compile_matchable_args) { + .ilist = ilist, + .milist = milist, + .local = true, })); - TRY(ilist_ensure_cap(compiler, body_ilist, 1)); - ilist_put_insn(body_ilist, INSN_MATCHER_DROP); - TRY(compile_body(compiler, &subfunc->body, body_ilist)); TRY(ilist_ensure_cap(compiler, ilist, 2)); diff --git a/src/context.c b/src/context.c index 8a6c020..6fdf28a 100644 --- a/src/context.c +++ b/src/context.c @@ -445,10 +445,7 @@ static void stack_traverse(struct stack stack, gc_visitor visitor, void *opaque) { for (size_t i = 0; i < stack.len; i++) { - struct gc_object *object = apfl_value_get_gc_object(stack.items[i]); - if (object != NULL) { - visitor(opaque, object); - } + apfl_value_visit_gc_object(stack.items[i], visitor, opaque); } } @@ -460,6 +457,72 @@ visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque) } } +static void +visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque) +{ + visit_nullable_scope(scopes.local, visitor, opaque); + visit_nullable_scope(scopes.closure, visitor, opaque); +} + +static void +visit_matcher_stack(struct matcher_stack matcher_stack, gc_visitor visitor, void *opaque) +{ + for (size_t i = 0; i < matcher_stack.len; i++) { + visitor( + opaque, + GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER) + ); + } +} + +static void +visit_func_cse(struct func_call_stack_entry cse, gc_visitor visitor, void *opaque) +{ + visitor( + opaque, + GC_OBJECT_FROM(cse.instructions, GC_TYPE_INSTRUCTIONS) + ); + + visit_scopes(cse.scopes, visitor, opaque); + visit_matcher_stack(cse.matcher_stack, visitor, opaque); +} + +static void +visit_cfunc_cse(struct cfunc_call_stack_entry cse, gc_visitor visitor, void *opaque) +{ + visitor( + opaque, + GC_OBJECT_FROM(cse.func, GC_TYPE_CFUNC) + ); +} + +static void +visit_matcher_cse(struct matcher_call_stack_entry cse, gc_visitor visitor, void* opaque) +{ + visitor( + opaque, + GC_OBJECT_FROM(cse.matcher, GC_TYPE_MATCHER) + ); + visit_scopes(cse.scopes, visitor, opaque); + for (size_t i = 0; i < cse.capture_count; i++) { + apfl_value_visit_gc_object(cse.captures[i], visitor, opaque); + visitor( + opaque, + GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING) + ); + } +} + +static void +visit_func_dispatch_cse(struct func_dispatch_call_stack_entry cse, gc_visitor visitor, void *opaque) +{ + visitor( + opaque, + GC_OBJECT_FROM(cse.function, GC_TYPE_FUNC) + ); + visit_scopes(cse.scopes, visitor, opaque); +} + static void gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, void *opaque) { @@ -467,41 +530,18 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo switch (cse.type) { case CSE_FUNCTION: - visitor( - opaque, - GC_OBJECT_FROM(cse.func.instructions, GC_TYPE_INSTRUCTIONS) - ); - - visit_nullable_scope(cse.func.scope, visitor, opaque); - visit_nullable_scope(cse.func.closure_scope, visitor, opaque); - - if (cse.func.matcher != NULL) { - visitor( - opaque, - GC_OBJECT_FROM(cse.func.matcher, GC_TYPE_MATCHER) - ); - } + visit_func_cse(cse.func, visitor, opaque); break; case CSE_CFUNCTION: - visitor( - opaque, - GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC) - ); + visit_cfunc_cse(cse.cfunc, visitor, opaque); break; case CSE_MATCHER: - visitor( - opaque, - GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER) - ); + visit_matcher_cse(cse.matcher, visitor, opaque); break; case CSE_FUNCTION_DISPATCH: - visitor( - opaque, - GC_OBJECT_FROM(cse.func_dispatch.function, GC_TYPE_FUNC) - ); + visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque); break; } - } static void @@ -541,6 +581,23 @@ deinit_stack(struct apfl_allocator allocator, struct stack *stack) *stack = apfl_stack_new(); } +static void +func_call_stack_entry_deinit(struct apfl_allocator allocator, struct func_call_stack_entry *cse) +{ + FREE_LIST(allocator, cse->matcher_stack.items, cse->matcher_stack.cap); +} + +void +apfl_matcher_call_stack_entry_deinit(struct apfl_allocator allocator, struct matcher_call_stack_entry *cse) +{ + FREE_LIST(allocator, cse->captures, cse->capture_count); + cse->captures = NULL; + FREE_LIST(allocator, cse->transfers, cse->capture_count); + cse->transfers = NULL; + FREE_LIST(allocator, cse->matcher_state_stack, cse->matcher_state_stack_cap); + cse->matcher_state_stack = NULL; +} + void apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry) { @@ -548,11 +605,13 @@ apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_ switch (entry->type) { case CSE_FUNCTION: + func_call_stack_entry_deinit(allocator, &entry->func); + break; case CSE_CFUNCTION: case CSE_FUNCTION_DISPATCH: break; case CSE_MATCHER: - FREE_LIST(allocator, entry->matcher.matcher_state_stack, entry->matcher.matcher_state_stack_cap); + apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher); break; } } @@ -988,8 +1047,8 @@ apfl_dict_set( assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index})); } -void -apfl_get_member( +bool +apfl_get_member_if_exists( apfl_ctx ctx, apfl_stackidx container_index, apfl_stackidx k_index @@ -1011,17 +1070,16 @@ apfl_get_member( enum get_item_result result = apfl_value_get_item(container, k, value); if (result != GET_ITEM_OK) { - assert(apfl_stack_drop(ctx, -1)); + assert(apfl_stack_drop(ctx, -1)); // Drop the placeholder } assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index})); switch (result) { case GET_ITEM_OK: - break; + return true; case GET_ITEM_KEY_DOESNT_EXIST: - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist); - break; + return false; case GET_ITEM_NOT_A_CONTAINER: apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_is_not_a_container); break; @@ -1029,6 +1087,20 @@ apfl_get_member( apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_key_type); break; } + + assert(false); + return false; +} + +void +apfl_get_member( + apfl_ctx ctx, + apfl_stackidx container_index, + apfl_stackidx k_index +) { + if (!apfl_get_member_if_exists(ctx, container_index, k_index)) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist); + } } void @@ -1180,7 +1252,7 @@ apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b) } static struct scope * -closure_scope_for_func_inner(apfl_ctx ctx) +closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes) { struct scope *out = apfl_scope_new(&ctx->gc); if (out == NULL) { @@ -1191,22 +1263,18 @@ closure_scope_for_func_inner(apfl_ctx ctx) return NULL; } - struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx); - if (entry == NULL || entry->type != CSE_FUNCTION) { - return out; - } + // The order is important here: by merging the local scope last, we make + // sure a variable from the current scope shadows a variable from the + // closure scope - // The order is important here: by merging entry->scope last, we make sure a - // variable from the current scope shadows a variable from the closure scope - - if (entry->func.closure_scope != NULL) { - if (!apfl_scope_merge_into(out, entry->func.closure_scope)) { + if (scopes.closure != NULL) { + if (!apfl_scope_merge_into(out, scopes.closure)) { return NULL; } } - if (entry->func.scope != NULL) { - if (!apfl_scope_merge_into(out, entry->func.scope)) { + if (scopes.local != NULL) { + if (!apfl_scope_merge_into(out, scopes.local)) { return NULL; } } @@ -1215,10 +1283,10 @@ closure_scope_for_func_inner(apfl_ctx ctx) } struct scope * -apfl_closure_scope_for_func(apfl_ctx ctx) +apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - struct scope *out = closure_scope_for_func_inner(ctx); + struct scope *out = closure_scope_for_func_inner(ctx, scopes); apfl_gc_tmproots_restore(&ctx->gc, tmproots); return out; } @@ -1350,3 +1418,64 @@ struct apfl_format_writer apfl_get_output_writer(apfl_ctx ctx) { return ctx->output_writer; } + +static bool +init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len) +{ + if (len == 0) { + return true; + } + + if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) { + return false; + } + + for (size_t i = 0; i < len; i++) { + (*list)[i] = (struct apfl_value) { .type = VALUE_NIL }; + } + return true; +} + +struct matcher * +apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist) +{ + struct matcher matcher = { + .instructions = milist, + .value_count = 0, + .values = NULL, + }; + + if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) { + goto error; + } + matcher.value_count = milist->value_count; + + struct matcher *gc_matcher = apfl_gc_new_matcher(gc); + if (gc_matcher == NULL) { + goto error; + } + + *gc_matcher = matcher; + + return gc_matcher; + +error: + apfl_matcher_deinit(gc->allocator, &matcher); + return NULL; +} + +void +apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher) +{ + FREE_LIST(allocator, matcher->values, matcher->value_count); +} + +void +apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque) +{ + visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS)); + + for (size_t i = 0; i < matcher->instructions->value_count; i++) { + apfl_value_visit_gc_object(matcher->values[i], visitor, opaque); + } +} diff --git a/src/context.h b/src/context.h index cd32d50..a9e7496 100644 --- a/src/context.h +++ b/src/context.h @@ -11,18 +11,42 @@ extern "C" { #include "bytecode.h" #include "hashmap.h" #include "gc.h" -#include "matcher.h" #include "value.h" #include "scope.h" #define RESULT_OFF_FOR_LONGJMP 1 +struct scopes { + // Both scope and closure_scope can be null + struct scope *local; + struct scope *closure; +}; + +struct matcher_capture_transfer { + struct apfl_string *var; + size_t path_start; + size_t path_len; + bool local; +}; + +struct matcher { + struct matcher_instruction_list *instructions; + size_t value_count; + struct apfl_value *values; +}; + struct stack { struct apfl_value *items; size_t len; size_t cap; }; +struct matcher_stack { + struct matcher **items; + size_t len; + size_t cap; +}; + enum call_stack_entry_type { CSE_FUNCTION, CSE_CFUNCTION, @@ -34,14 +58,14 @@ struct func_call_stack_entry { size_t pc; struct instruction_list *instructions; - // Both scope and closure_scope can be null (scope will be created lazily) - struct scope *scope; - struct scope *closure_scope; + // scopes.scope will be created lazily + struct scopes scopes; int execution_line; - struct matcher *matcher; + struct matcher_stack matcher_stack; bool returning_from_matcher; + bool matcher_result; }; struct cfunc_call_stack_entry { @@ -67,6 +91,12 @@ struct matcher_state { struct matcher_call_stack_entry { size_t pc; struct matcher *matcher; + struct scopes scopes; + + size_t capture_index; + size_t capture_count; + struct apfl_value *captures; + struct matcher_capture_transfer *transfers; struct matcher_state *matcher_state_stack; size_t matcher_state_stack_len; @@ -75,7 +105,9 @@ struct matcher_call_stack_entry { struct func_dispatch_call_stack_entry { size_t subfunc; + struct scopes scopes; bool returning_from_matcher; + bool matcher_result; struct function *function; }; @@ -127,6 +159,7 @@ struct apfl_ctx_data { struct apfl_format_writer output_writer; }; +void apfl_matcher_call_stack_entry_deinit(struct apfl_allocator, struct matcher_call_stack_entry *); void apfl_call_stack_entry_deinit(struct apfl_allocator, struct call_stack_entry *); struct stack apfl_stack_new(void); @@ -147,13 +180,17 @@ bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string); struct call_stack_entry *apfl_call_stack_cur_entry(apfl_ctx); -struct scope *apfl_closure_scope_for_func(apfl_ctx); +struct scope *apfl_closure_scope_for_func(apfl_ctx, struct scopes); struct apfl_format_writer apfl_get_output_writer(apfl_ctx); bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner); void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner); +struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *); +void apfl_matcher_deinit(struct apfl_allocator, struct matcher *); +void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *); + void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, void *); #ifdef __cplusplus diff --git a/src/error.c b/src/error.c index 4a05879..759a4f5 100644 --- a/src/error.c +++ b/src/error.c @@ -58,8 +58,6 @@ 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 ""; @@ -75,8 +73,6 @@ apfl_error_as_const_string(struct apfl_error error) return apfl_messages.input_error_while_parsing; case APFL_ERR_UNEXPECTED_EOF: return apfl_messages.unexpected_end_of_file; - case APFL_ERR_NOT_IMPLEMENTED: - return apfl_messages.feature_not_implemented; default: return NULL; } @@ -186,9 +182,6 @@ format_error(struct apfl_format_writer w, struct apfl_error error) TRY(apfl_format_put_string(w, "Unexpected blank (\"_\") in member access near ")); TRY(apfl_format_put_pos(w, error.position)); return true; - case APFL_ERR_NOT_IMPLEMENTED: - TRY(apfl_format_put_string(w, apfl_messages.feature_not_implemented)); - return true; } TRY(apfl_format_put_string(w, "Unknown error ")); diff --git a/src/eval.c b/src/eval.c index 2d2e354..5d877b1 100644 --- a/src/eval.c +++ b/src/eval.c @@ -8,7 +8,6 @@ #include "context.h" #include "format.h" #include "hashmap.h" -#include "matcher.h" #include "resizable.h" #include "strings.h" #include "value.h" @@ -16,7 +15,7 @@ static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse); static void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse); static void dispatch(apfl_ctx ctx, struct call_stack_entry *cse); -static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher); +static void matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes); static void stack_must_drop(apfl_ctx ctx, apfl_stackidx index) @@ -61,19 +60,6 @@ must_get_matcher_argument(apfl_ctx ctx, size_t *i, struct matcher_instruction_li ABSTRACT_MUST_GET_ARG(get_matcher_argument, ctx, i, milist, arg) } -static struct func_call_stack_entry * -get_current_func_cse(apfl_ctx ctx) -{ - struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); - if (cse == NULL) { - return NULL; - } - - return cse->type == CSE_FUNCTION - ? &cse->func - : NULL; -} - enum scope_type { SCOPE_LOCAL, SCOPE_CLOSUE, @@ -81,21 +67,13 @@ enum scope_type { }; static struct scope * -get_scope(apfl_ctx ctx, enum scope_type type) +get_scope(apfl_ctx ctx, struct scopes scopes, enum scope_type type) { - struct func_call_stack_entry *func_cse; - switch (type) { case SCOPE_LOCAL: - if ((func_cse = get_current_func_cse(ctx)) != NULL) { - return func_cse->scope; - } - return NULL; + return scopes.local; case SCOPE_CLOSUE: - if ((func_cse = get_current_func_cse(ctx)) != NULL) { - return func_cse->closure_scope; - } - return NULL; + return scopes.closure; case SCOPE_GLOBAL: return ctx->globals; } @@ -105,29 +83,30 @@ get_scope(apfl_ctx ctx, enum scope_type type) } static struct scope * -get_or_create_local_scope(apfl_ctx ctx) +get_or_create_local_scope(apfl_ctx ctx, struct scopes *scopes) { - struct func_call_stack_entry *func_cse = get_current_func_cse(ctx); - assert(func_cse != NULL); - - if (func_cse->scope != NULL) { - return func_cse->scope; + if (scopes->local != NULL) { + return scopes->local; } - if ((func_cse->scope = apfl_scope_new(&ctx->gc)) == NULL) { + if ((scopes->local = apfl_scope_new(&ctx->gc)) == NULL) { apfl_raise_alloc_error(ctx); } - return func_cse->scope; + return scopes->local; } static bool -try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type) -{ +try_variable_get_for_scope_type( + apfl_ctx ctx, + struct scopes scopes, + struct apfl_string *name, + enum scope_type type +) { struct apfl_value value; struct scope *scope; - if ((scope = get_scope(ctx, type)) != NULL) { + if ((scope = get_scope(ctx, scopes, type)) != NULL) { if (apfl_scope_get(scope, name, &value)) { apfl_stack_must_push(ctx, value); return true; @@ -137,15 +116,15 @@ try_variable_get_for_scope_type(apfl_ctx ctx, struct apfl_string *name, enum sco } static void -variable_get(apfl_ctx ctx, struct apfl_string *name) +variable_get(apfl_ctx ctx, struct scopes scopes, struct apfl_string *name, bool global) { - if (try_variable_get_for_scope_type(ctx, name, SCOPE_LOCAL)) { + if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_LOCAL)) { return; } - if (try_variable_get_for_scope_type(ctx, name, SCOPE_CLOSUE)) { + if (try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_CLOSUE)) { return; } - if (try_variable_get_for_scope_type(ctx, name, SCOPE_GLOBAL)) { + if (global && try_variable_get_for_scope_type(ctx, scopes, name, SCOPE_GLOBAL)) { return; } @@ -154,33 +133,28 @@ variable_get(apfl_ctx ctx, struct apfl_string *name) static bool try_variable_update_existing_for_scope_type( - apfl_ctx ctx, struct apfl_string *name, struct apfl_value value, - enum scope_type type + struct scope *scope ) { - struct scope *scope; - - if ((scope = get_scope(ctx, type)) != NULL) { - if (apfl_scope_update_existing(scope, name, value)) { - return true; - } + if (scope == NULL) { + return false; } - return false; + return apfl_scope_update_existing(scope, name, value); } static void -variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct apfl_value value) +variable_set_value(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local, struct apfl_value value) { bool was_set = false; if (!local) { - was_set = try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_LOCAL) - || try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_CLOSUE); + was_set = try_variable_update_existing_for_scope_type(name, value, scopes->local) + || try_variable_update_existing_for_scope_type(name, value, scopes->closure); } if (!was_set) { - struct scope *scope = get_or_create_local_scope(ctx); + struct scope *scope = get_or_create_local_scope(ctx, scopes); assert(scope != NULL /*get_or_create_local_scope should never return NULL*/); if (!apfl_scope_set(&ctx->gc, scope, name, value)) { @@ -190,11 +164,11 @@ variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct ap } static void -variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local) +variable_set(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool keep_on_stack, bool local) { struct apfl_value value = apfl_stack_must_get(ctx, -1); - variable_set_value(ctx, name, local, value); + variable_set_value(ctx, scopes, name, local, value); if (keep_on_stack) { // If the value should be kept on the stack, the value is now in two @@ -206,42 +180,88 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool lo } } -static bool -variable_exists_by_scope_type(apfl_ctx ctx, struct apfl_string *name, enum scope_type type) -{ - struct scope *scope; - - if ((scope = get_scope(ctx, type)) != NULL) { - if (apfl_scope_has(scope, name)) { - return true; - } - } - - return false; -} - static void -variable_new(apfl_ctx ctx, struct apfl_string *name, bool local) +variable_new(apfl_ctx ctx, struct scopes *scopes, struct apfl_string *name, bool local) { if (!local) { - if (variable_exists_by_scope_type(ctx, name, SCOPE_LOCAL)) { + if (scopes->local != NULL && apfl_scope_has(scopes->local, name)) { return; } - if (variable_exists_by_scope_type(ctx, name, SCOPE_CLOSUE)) { + if (scopes->closure != NULL && apfl_scope_has(scopes->closure, name)) { return; } } - struct scope *scope = get_or_create_local_scope(ctx); + struct scope *scope = get_or_create_local_scope(ctx, scopes); if (!apfl_scope_create_var(&ctx->gc, scope, name)) { apfl_raise_alloc_error(ctx); } } static void -func_inner(apfl_ctx ctx, size_t count) +matcher_push(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist) { - struct scope *scope = apfl_closure_scope_for_func(ctx); + struct matcher_stack *matcher_stack = &cse->matcher_stack; + if (!apfl_resizable_ensure_cap_for_more_elements( + ctx->gc.allocator, + sizeof(struct matcher *), + (void **)&matcher_stack->items, + matcher_stack->len, + &matcher_stack->cap, + 1 + )) { + apfl_raise_alloc_error(ctx); + } + + if ((matcher_stack->items[matcher_stack->len] = apfl_matcher_new(&ctx->gc, milist)) == NULL) { + apfl_raise_alloc_error(ctx); + } + + matcher_stack->len++; +} + +static struct matcher * +matcher_stack_top(apfl_ctx ctx, struct func_call_stack_entry *cse) +{ + struct matcher_stack *matcher_stack = &cse->matcher_stack; + if (matcher_stack->len == 0) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } + + struct matcher *matcher = matcher_stack->items[matcher_stack->len-1]; + + if (matcher == NULL) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } + + return matcher; +} + +static void +matcher_stack_drop(apfl_ctx ctx, struct func_call_stack_entry *cse) +{ + struct matcher_stack *matcher_stack = &cse->matcher_stack; + if (matcher_stack->len == 0) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } + + assert( + // We're shrinking, should not fail + apfl_resizable_resize( + ctx->gc.allocator, + sizeof(struct matcher *), + (void **)&matcher_stack->items, + &matcher_stack->len, + &matcher_stack->cap, + matcher_stack->len-1 + ) + ); +} + +static void +func_inner(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count) +{ + struct scope *scope = apfl_closure_scope_for_func(ctx, cse->scopes); if (scope == NULL) { apfl_raise_alloc_error(ctx); } @@ -264,10 +284,10 @@ func_inner(apfl_ctx ctx, size_t count) } static void -func(apfl_ctx ctx, size_t count) +func(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t count) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - func_inner(ctx, count); + func_inner(ctx, cse, count); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } @@ -281,21 +301,17 @@ func_add_subfunc(apfl_ctx ctx, struct func_call_stack_entry *cse, struct instruc apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } - if (cse->matcher == NULL) { + if (!apfl_func_add_subfunc(value.func, body, matcher_stack_top(ctx, cse))) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } - if (!apfl_func_add_subfunc(value.func, body, cse->matcher)) { - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); - } - - cse->matcher = NULL; + matcher_stack_drop(ctx, cse); } -static void -call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) +static bool +try_call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) { - if (!apfl_resizable_append( + return apfl_resizable_append( ctx->gc.allocator, sizeof(struct call_stack_entry), (void**)&ctx->call_stack.items, @@ -303,7 +319,13 @@ call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) &ctx->call_stack.cap, &cse, 1 - )) { + ); +} + +static void +call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) +{ + if (!try_call_stack_push(ctx, cse)) { apfl_call_stack_entry_deinit(ctx->gc.allocator, &cse); apfl_raise_alloc_error(ctx); } @@ -425,14 +447,28 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid } switch (func.type) { - case VALUE_FUNC: + case VALUE_FUNC: { + struct scope *local_scope = apfl_scope_new(&ctx->gc); + if (local_scope == NULL) { + apfl_raise_alloc_error(ctx); + } + + if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(local_scope, GC_TYPE_SCOPE))) { + apfl_raise_alloc_error(ctx); + } + prepare_call(ctx, tmproots, args, (struct call_stack_entry) { .type = CSE_FUNCTION_DISPATCH, .stack = apfl_stack_new(), .func_dispatch = { .subfunc = 0, + .scopes = { + .local = local_scope, + .closure = func.func->scope, + }, .function = func.func, .returning_from_matcher = false, + .matcher_result = false, }, }); @@ -444,6 +480,7 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid evaluate_until_call_stack_return(ctx); } break; + } case VALUE_CFUNC: prepare_call(ctx, tmproots, args, (struct call_stack_entry) { .type = CSE_CFUNCTION, @@ -475,23 +512,10 @@ apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index) call(ctx, func_index, args_index, false); } - static void -matcher_load(apfl_ctx ctx, struct func_call_stack_entry *cse, struct matcher_instruction_list *milist) +matcher_set_val(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t index) { - assert(cse != NULL); - - if ((cse->matcher = apfl_matcher_new(&ctx->gc, milist)) == NULL) { - apfl_raise_alloc_error(ctx); - } -} - -static void -matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index) -{ - if (matcher == NULL) { - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); - } + struct matcher *matcher = matcher_stack_top(ctx, cse); if (index >= matcher->instructions->value_count) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); @@ -501,37 +525,17 @@ matcher_set_val(apfl_ctx ctx, struct matcher *matcher, size_t index) } static void -variable_set_from_matcher_inner( - apfl_ctx ctx, - struct matcher *matcher, - struct apfl_string *varname, - size_t index, - bool local -) { - if (matcher == NULL) { - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); - } - - if (index >= matcher->instructions->capture_count) { - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); - } - - struct apfl_value value = apfl_value_move(&matcher->captures[index]); - must_tmproot_add_value(ctx, value); - - variable_set_value(ctx, varname, local, value); -} - -static void -variable_set_from_matcher( - apfl_ctx ctx, - struct matcher *matcher, - struct apfl_string *varname, - size_t index, - bool local -) { +matcher_must_match(apfl_ctx ctx, struct func_call_stack_entry *cse) +{ size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - variable_set_from_matcher_inner(ctx, matcher, varname, index, local); + + struct matcher *matcher = matcher_stack_top(ctx, cse); + if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER))) { + apfl_raise_alloc_error(ctx); + } + matcher_stack_drop(ctx, cse); + matcher_init_matching(ctx, matcher, cse->scopes); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); } @@ -539,16 +543,13 @@ static void evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) { if (cse->returning_from_matcher) { - assert(cse->matcher != NULL); - if (!cse->matcher->result) { - cse->matcher = NULL; + if (!cse->matcher_result) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_doesnt_match); } cse->returning_from_matcher = false; } union instruction_or_arg arg; - union instruction_or_arg arg2; size_t *pc = &cse->pc; struct instruction_list *ilist = cse->instructions; @@ -596,27 +597,27 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) goto continue_loop; case INSN_VAR_NEW: must_get_argument(ctx, pc, ilist, &arg); - variable_new(ctx, arg.string, false); + variable_new(ctx, &cse->scopes, arg.string, false); goto continue_loop; case INSN_VAR_NEW_LOCAL: must_get_argument(ctx, pc, ilist, &arg); - variable_new(ctx, arg.string, true); + variable_new(ctx, &cse->scopes, arg.string, true); goto continue_loop; case INSN_VAR_GET: must_get_argument(ctx, pc, ilist, &arg); - variable_get(ctx, arg.string); + variable_get(ctx, cse->scopes, arg.string, true); goto continue_loop; case INSN_VAR_SET: must_get_argument(ctx, pc, ilist, &arg); - variable_set(ctx, arg.string, true, false); + variable_set(ctx, &cse->scopes, arg.string, true, false); goto continue_loop; case INSN_VAR_SET_LOCAL: must_get_argument(ctx, pc, ilist, &arg); - variable_set(ctx, arg.string, true, true); + variable_set(ctx, &cse->scopes, arg.string, true, true); goto continue_loop; case INSN_MOVE_TO_LOCAL_VAR: must_get_argument(ctx, pc, ilist, &arg); - variable_set(ctx, arg.string, false, true); + variable_set(ctx, &cse->scopes, arg.string, false, true); goto continue_loop; case INSN_NEXT_LINE: cse->execution_line++; @@ -634,6 +635,9 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) apfl_raise_invalid_stackidx(ctx); } goto continue_loop; + case INSN_DUP: + apfl_copy(ctx, -1); + goto continue_loop; case INSN_CALL: call(ctx, -2, -1, true); @@ -643,42 +647,28 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) return; case INSN_FUNC: must_get_argument(ctx, pc, ilist, &arg); - func(ctx, arg.count); + func(ctx, cse, arg.count); goto continue_loop; case INSN_FUNC_ADD_SUBFUNC: must_get_argument(ctx, pc, ilist, &arg); func_add_subfunc(ctx, cse, arg.body); goto continue_loop; - case INSN_MATCHER_LOAD: + case INSN_MATCHER_PUSH: must_get_argument(ctx, pc, ilist, &arg); - matcher_load(ctx, cse, arg.matcher); + matcher_push(ctx, cse, arg.matcher); goto continue_loop; case INSN_MATCHER_SET_VAL: must_get_argument(ctx, pc, ilist, &arg); - matcher_set_val(ctx, cse->matcher, arg.index); + matcher_set_val(ctx, cse, arg.index); goto continue_loop; case INSN_MATCHER_MUST_MATCH: - // matcher_init_matching pushes a new call stack entry for the matcher onto the stack. We rturn from this + // matcher_must_match pushes a new call stack entry for the matcher onto the stack. We return from this // So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher, // once it returns. - cse->returning_from_matcher = true; - matcher_init_matching(ctx, cse->matcher); + matcher_must_match(ctx, cse); return; - case INSN_MATCHER_DROP: - cse->matcher = NULL; - goto continue_loop; - case INSN_VAR_SET_FROM_MATCHER: - must_get_argument(ctx, pc, ilist, &arg); - must_get_argument(ctx, pc, ilist, &arg2); - variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, false); - goto continue_loop; - case INSN_VAR_SET_LOCAL_FROM_MATCHER: - must_get_argument(ctx, pc, ilist, &arg); - must_get_argument(ctx, pc, ilist, &arg2); - variable_set_from_matcher(ctx, cse->matcher, arg.string, arg2.index, true); - goto continue_loop; } assert(false); @@ -732,41 +722,99 @@ matcher_state_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse) } static void -matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher) +matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes) { - struct apfl_value value = apfl_stack_must_get(ctx, -1); + struct apfl_value value = apfl_stack_must_pop(ctx, -1); must_tmproot_add_value(ctx, value); if (matcher == NULL) { apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); } + size_t capture_count = matcher->instructions->capture_count; struct matcher_call_stack_entry matcher_cse = { .pc = 0, .matcher = matcher, + .scopes = scopes, + .capture_index = 0, + .capture_count = capture_count, + .captures = NULL, + .transfers = NULL, .matcher_state_stack = NULL, .matcher_state_stack_len = 0, .matcher_state_stack_cap = 0, }; - matcher_state_push(ctx, &matcher_cse, (struct matcher_state) { - .mode = MATCHER_MODE_VALUE, - }); + if (capture_count > 0) { + if ((matcher_cse.captures = ALLOC_LIST(ctx->gc.allocator, struct apfl_value, capture_count)) == NULL) { + goto error; + } - call_stack_push(ctx, (struct call_stack_entry) { + for (size_t i = 0; i < capture_count; i++) { + matcher_cse.captures[i] = (struct apfl_value) { .type = VALUE_NIL }; + } + + if ((matcher_cse.transfers = ALLOC_LIST( + ctx->gc.allocator, + struct matcher_capture_transfer, + capture_count + )) == NULL) { + goto error; + } + + for (size_t i = 0; i < capture_count; i++) { + matcher_cse.transfers[i] = (struct matcher_capture_transfer) { + .var = NULL, + .path_start = 0, + .path_len = 0, + .local = false, + }; + } + } + + if ((matcher_cse.matcher_state_stack = ALLOC_LIST( + ctx->gc.allocator, + struct matcher_state, + 1 + )) == NULL) { + goto error; + } + + matcher_cse.matcher_state_stack[0] = (struct matcher_state) { + .mode = MATCHER_MODE_VALUE, + }; + matcher_cse.matcher_state_stack_len = 1; + matcher_cse.matcher_state_stack_cap = 1; + + if (!try_call_stack_push(ctx, (struct call_stack_entry) { .type = CSE_MATCHER, .stack = apfl_stack_new(), .matcher = matcher_cse, - }); + })) { + goto error; + } + // No need for `goto error` on failure here, all dynamically allocated + // elements are on the call stack now, so the GC can clean them up in case + // of an error. apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value)); + + return; + +error: + apfl_matcher_call_stack_entry_deinit(ctx->gc.allocator, &matcher_cse); + apfl_raise_alloc_error(ctx); } +/* + * Initialise matching. Pushes a new call stack and pops a value of the current+ + * value stack. + */ static void -matcher_init_matching(apfl_ctx ctx, struct matcher *matcher) +matcher_init_matching(apfl_ctx ctx, struct matcher *matcher, struct scopes scopes) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - matcher_init_matching_inner(ctx, matcher); + matcher_init_matching_inner(ctx, matcher, scopes); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } @@ -978,6 +1026,63 @@ matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse) return matcher_next(ctx, cse); } +static void +matcher_transfer(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + for (size_t i = 0; i < cse->capture_count; i++) { + struct matcher_capture_transfer transfer = cse->transfers[i]; + if (transfer.path_len == 0) { + variable_set_value(ctx, &cse->scopes, transfer.var, transfer.local, cse->captures[i]); + } else { + // Set the value at a key path in a (nested) dictionary. + + variable_get(ctx, cse->scopes, transfer.var, false); + if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict); + } + + + // Get or create intermediary dictionaries along the key path and leave a copy of the previous one on the + // stack, so we can set the result in reverse order there later. + for (size_t i = 0; i < transfer.path_len - 1; i++) { + apfl_copy(ctx, -1); + + size_t value_index = transfer.path_start + i; + matcher_check_index(ctx, cse->matcher->value_count, value_index); + apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index])); + if (apfl_get_member_if_exists(ctx, -2, -1)) { + if (apfl_get_type(ctx, -1) != APFL_VALUE_DICT) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict); + } + } else { + apfl_dict_create(ctx); + } + } + + // Set the value to the rightmost dictionary key + size_t value_index = transfer.path_start + transfer.path_len - 1; + matcher_check_index(ctx, cse->matcher->value_count, value_index); + apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index])); + apfl_stack_must_push(ctx, cse->captures[i]); + apfl_dict_set(ctx, -3, -2, -1); + + // Go through the key path (minus the rightmost key) in reverse order and set the value in the intermediary + // dictionaries. Note that i has a offset of one here so we can use the i > 0 check in the loop (>= 0 would + // not work as size_t is unsigned). + for (size_t i = transfer.path_len - 1; i > 0; i--) { + size_t value_index = transfer.path_start + i - 1; + matcher_check_index(ctx, cse->matcher->value_count, value_index); + apfl_stack_must_push(ctx, apfl_value_set_cow_flag(cse->matcher->values[value_index])); + + apfl_dict_set(ctx, -3, -1, -2); + } + + // Finally set the copied and modified dictionary to the variable again. + variable_set(ctx, &cse->scopes, transfer.var, false, false); + } + } +} + static void return_from_matcher(apfl_ctx ctx, bool result) { @@ -985,12 +1090,27 @@ return_from_matcher(apfl_ctx ctx, bool result) assert(cse != NULL); assert(cse->type == CSE_MATCHER); - cse->matcher.matcher->result = result; + if (result) { + matcher_transfer(ctx, &cse->matcher); + } + call_stack_drop(ctx); cse = apfl_call_stack_cur_entry(ctx); assert(cse != NULL); - assert(cse->type == CSE_FUNCTION || cse->type == CSE_FUNCTION_DISPATCH); + + switch (cse->type) { + case CSE_FUNCTION: + cse->func.returning_from_matcher = true; + cse->func.matcher_result = result; + break; + case CSE_FUNCTION_DISPATCH: + cse->func_dispatch.returning_from_matcher = true; + cse->func_dispatch.matcher_result = result; + break; + default: + assert(false /* Invalid stack entry below matcher stack */); + } } #define RETURN_WITHOUT_MATCH(ctx) \ @@ -1006,6 +1126,53 @@ return_from_matcher(apfl_ctx ctx, bool result) } \ } while (0) +static bool +matcher_evaluate_capturing_instruction( + apfl_ctx ctx, + struct matcher_call_stack_entry *cse, + bool local, + bool with_path +) { + size_t *pc = &cse->pc; + struct matcher *matcher = cse->matcher; + struct matcher_instruction_list *milist = matcher->instructions; + + struct matcher_capture_transfer transfer = { + .var = NULL, + .path_start = 0, + .path_len = 0, + .local = local, + }; + + union matcher_instruction_or_arg arg; + + must_get_matcher_argument(ctx, pc, milist, &arg); + transfer.var = arg.string; + + if (with_path) { + must_get_matcher_argument(ctx, pc, milist, &arg); + transfer.path_start = arg.index; + must_get_matcher_argument(ctx, pc, milist, &arg); + transfer.path_len = arg.index; + } + + struct apfl_value cur; + + if (!matcher_current_val(ctx, cse, &cur)) { + return_from_matcher(ctx, false); + return false; + } + + size_t capture = cse->capture_index++; + matcher_check_index(ctx, milist->capture_count, capture); + + cse->captures[capture] = apfl_value_set_cow_flag(cur); + cse->transfers[capture] = transfer; + + return matcher_next(ctx, cse); +} + + static void evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) { @@ -1019,14 +1186,29 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) struct apfl_value cur; switch (milist->instructions[(*pc)++].instruction) { - case MATCHER_CAPTURE: - if (!matcher_current_val(ctx, cse, &cur)) { - RETURN_WITHOUT_MATCH(ctx); - } - must_get_matcher_argument(ctx, pc, milist, &arg); - matcher_check_index(ctx, milist->capture_count, arg.index); - matcher->captures[arg.index] = apfl_value_set_cow_flag(cur); - RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse)); + case MATCHER_CAPTURE_TO_VAR: + RETURN_WITHOUT_MATCH_ON_FALSE( + ctx, + matcher_evaluate_capturing_instruction(ctx, cse, false, false) + ); + goto continue_loop; + case MATCHER_CAPTURE_TO_VAR_LOCAL: + RETURN_WITHOUT_MATCH_ON_FALSE( + ctx, + matcher_evaluate_capturing_instruction(ctx, cse, true, false) + ); + goto continue_loop; + case MATCHER_CAPTURE_TO_VAR_WITH_PATH: + RETURN_WITHOUT_MATCH_ON_FALSE( + ctx, + matcher_evaluate_capturing_instruction(ctx, cse, false, true) + ); + goto continue_loop; + case MATCHER_CAPTURE_TO_VAR_LOCAL_WITH_PATH: + RETURN_WITHOUT_MATCH_ON_FALSE( + ctx, + matcher_evaluate_capturing_instruction(ctx, cse, true, true) + ); goto continue_loop; case MATCHER_IGNORE: if (!matcher_current_val(ctx, cse, &cur)) { @@ -1075,6 +1257,16 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) ); } +struct matcher_stack +matcher_stack_new(void) +{ + return (struct matcher_stack) { + .items = NULL, + .len = 0, + .cap = 0, + }; +} + static void dispatch(apfl_ctx ctx, struct call_stack_entry *cse) { @@ -1084,19 +1276,20 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse) struct function *function = fd_cse->function; if (fd_cse->returning_from_matcher) { - struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc]; - if (subfunction->matcher->result) { + if (fd_cse->matcher_result) { + struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc]; + // Replace the current CSE with a function CSE cse->type = CSE_FUNCTION; cse->stack.len = 0; cse->func = (struct func_call_stack_entry) { .pc = 0, .instructions = subfunction->body, - .scope = NULL, - .closure_scope = function->scope, + .scopes = fd_cse->scopes, .execution_line = subfunction->body->line, - .matcher = subfunction->matcher, + .matcher_stack = matcher_stack_new(), .returning_from_matcher = false, + .matcher_result = false, }; return; @@ -1110,8 +1303,14 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse) apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.no_matching_subfunction); } - fd_cse->returning_from_matcher = true; - matcher_init_matching(ctx, function->subfunctions[fd_cse->subfunc].matcher); + // matcher_init_matching consumes the value on the top of the stack, we need + // to copy the value for further subfunctions. + apfl_copy(ctx, -1); + matcher_init_matching( + ctx, + function->subfunctions[fd_cse->subfunc].matcher, + fd_cse->scopes + ); } struct apfl_iterative_runner_data { @@ -1149,11 +1348,13 @@ iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr .func = { .pc = 0, .instructions = ilist, - .scope = runner->scope, - .closure_scope = NULL, + .scopes = { + .local = runner->scope, + .closure = NULL, + }, .execution_line = ilist->line, + .matcher_stack = matcher_stack_new(), .returning_from_matcher = false, - .matcher = NULL, }, }); evaluate_until_call_stack_return(ctx); diff --git a/src/functional-tests/dictionary-assignments.at b/src/functional-tests/dictionary-assignments.at new file mode 100644 index 0000000..87698a6 --- /dev/null +++ b/src/functional-tests/dictionary-assignments.at @@ -0,0 +1,78 @@ +===== script ===== +foo = [->] +foo.a = 1 +print foo.a + +print "" + +# Like every value in apfl, dictionaries are copied when passed around. +# Let's check if this semantic works by copying the dictionary and then modify +# the copy. +bar = foo +bar.a = 2 +print foo.a +print bar.a + +print "" + +# Same thing with passing as argument +{ d -> + d.a = 3 + print d.a +} foo +print foo.a + +print "" + +# And the other way around + +printcopy = { d -> + {print d.a} +} foo +foo.a = 4 +print foo.a +(printcopy) + +print "" + +# What about nested element access? +foo.x@(+ 1 2).y = "bar" +print foo.x + +print "" + +# And as part of List destructuring +k := 10 +l = [a foo.zzz@k] = [10 ({ k = 20 })] +print k +print l +print a +print foo.zzz + +===== output ===== +1 + +1 +2 + +3 +1 + +4 +1 + +[ + 3 -> [ + "y" -> "bar" + ] +] + +20 +[ + 10 + 20 +] +10 +[ + 10 -> 20 +] diff --git a/src/functional-tests/shadowing.at b/src/functional-tests/shadowing.at index 7ec78f2..e80843e 100644 --- a/src/functional-tests/shadowing.at +++ b/src/functional-tests/shadowing.at @@ -13,6 +13,21 @@ print "Outside:" print foo print bar +print "" + +# Do the same thing but now use complex assignables. They are compiled into different bytecode, so let's test this too +({ + [~_ foo] = [3] + [~_ bar] := [3] + + print "Inside:" + print foo + print bar +}) +print "Outside:" +print foo +print bar + ===== output ===== Inside: 2 @@ -20,3 +35,10 @@ Inside: Outside: 2 1 + +Inside: +3 +3 +Outside: +3 +1 diff --git a/src/gc.c b/src/gc.c index b1dd3f7..4e0b305 100644 --- a/src/gc.c +++ b/src/gc.c @@ -5,8 +5,8 @@ #include "alloc.h" #include "bytecode.h" #include "context.h" +#include "format.h" #include "gc.h" -#include "matcher.h" #include "resizable.h" #include "scope.h" #include "value.h" @@ -287,7 +287,7 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque) apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque); return; case GC_TYPE_MATCHER_INSTRUCTIONS: - // Intentionally left blank. Object doesn't reference other objects. + apfl_gc_matcher_instructions_traverse(&object->matcher_instructions, cb, opaque); return; case GC_TYPE_MATCHER: apfl_gc_matcher_traverse(&object->matcher, cb, opaque); diff --git a/src/matcher.c b/src/matcher.c deleted file mode 100644 index bfa7a87..0000000 --- a/src/matcher.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "alloc.h" -#include "gc.h" -#include "matcher.h" - -static bool -init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len) -{ - if (len == 0) { - return true; - } - - if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) { - return false; - } - - for (size_t i = 0; i < len; i++) { - (*list)[i] = (struct apfl_value) { .type = VALUE_NIL }; - } - return true; -} - -struct matcher * -apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist) -{ - struct matcher matcher = { - .instructions = milist, - .value_count = 0, - .capture_count = 0, - .values = NULL, - .captures = NULL, - .result = false, - }; - - if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) { - goto error; - } - matcher.value_count = milist->value_count; - - if (!init_values_list(gc->allocator, &matcher.captures, milist->capture_count)) { - goto error; - } - matcher.capture_count = milist->capture_count; - - struct matcher *gc_matcher = apfl_gc_new_matcher(gc); - if (gc_matcher == NULL) { - goto error; - } - - *gc_matcher = matcher; - - return gc_matcher; - -error: - apfl_matcher_deinit(gc->allocator, &matcher); - return NULL; -} - -void -apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher) -{ - FREE_LIST(allocator, matcher->values, matcher->value_count); - FREE_LIST(allocator, matcher->captures, matcher->capture_count); -} - -void -apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque) -{ - visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS)); - - for (size_t i = 0; i < matcher->instructions->value_count; i++) { - struct gc_object *object = apfl_value_get_gc_object(matcher->values[i]); - if (object != NULL) { - visitor(opaque, object); - } - } - - for (size_t i = 0; i < matcher->instructions->capture_count; i++) { - struct gc_object *object = apfl_value_get_gc_object(matcher->captures[i]); - if (object != NULL) { - visitor(opaque, object); - } - } -} diff --git a/src/matcher.h b/src/matcher.h deleted file mode 100644 index 36dbb93..0000000 --- a/src/matcher.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef APFL_MATCHER_H -#define APFL_MATCHER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "bytecode.h" -#include "gc.h" -#include "value.h" - -struct matcher { - struct matcher_instruction_list *instructions; - size_t value_count; - size_t capture_count; - struct apfl_value *values; - struct apfl_value *captures; - bool result; -}; - -struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *); -void apfl_matcher_deinit(struct apfl_allocator, struct matcher *); -void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/scope.c b/src/scope.c index 002424f..53e191e 100644 --- a/src/scope.c +++ b/src/scope.c @@ -65,10 +65,7 @@ apfl_scope_new(struct gc *gc) 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); - } + apfl_value_visit_gc_object(*var, cb, opaque); } void diff --git a/src/value.c b/src/value.c index 19c8460..ab58932 100644 --- a/src/value.c +++ b/src/value.c @@ -661,8 +661,8 @@ apfl_value_get_gc_object(struct apfl_value value) return NULL; } -static void -call_visitor_for_value(gc_visitor cb, void *opaque, struct apfl_value value) +void +apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque) { struct gc_object *child = apfl_value_get_gc_object(value); if (child != NULL) { @@ -674,7 +674,7 @@ 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]); + apfl_value_visit_gc_object(list->items[i], cb, opaque); } } @@ -687,8 +687,8 @@ apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque) struct apfl_value *v = apfl_hashmap_cursor_peek_value(cur); assert(v != NULL); - call_visitor_for_value(cb, opaque, *k); - call_visitor_for_value(cb, opaque, *v); + apfl_value_visit_gc_object(*k, cb, opaque); + apfl_value_visit_gc_object(*v, cb, opaque); } } diff --git a/src/value.h b/src/value.h index 0dbe8be..115ca04 100644 --- a/src/value.h +++ b/src/value.h @@ -133,6 +133,7 @@ void apfl_cfunction_deinit(struct apfl_allocator, struct cfunction *); // Functions for garbage collection struct gc_object *apfl_value_get_gc_object(struct apfl_value); +void apfl_value_visit_gc_object(struct apfl_value value, gc_visitor cb, void *opaque); void apfl_gc_list_traverse(struct list_header *, gc_visitor, void *); void apfl_gc_dict_traverse(struct dict_header *, gc_visitor, void *); void apfl_gc_func_traverse(struct function*, gc_visitor, void *);