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 *);