From 8ca24bcd4911eaf4529f88c711e4ce1252d590c0 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sat, 13 Aug 2022 00:50:26 +0200 Subject: [PATCH] Implement subfunctions and argument matching A function can now have multiple subfunctions with their own parameter list. These parameters are now no longer constrained to variables and blanks only, but can also be consts and list destructurings (predicates are also already compiled but don't get evaluated yet). The first subfunction that matches the argument list gets evaluated. --- src/apfl.h | 1 + src/bytecode.c | 26 ++-- src/bytecode.h | 6 +- src/compile.c | 393 +++++++++++++++++++++++++++++++------------------ src/context.c | 7 + src/context.h | 8 + src/eval.c | 94 ++++++++++-- src/globals.c | 16 +- src/messages.c | 1 + src/value.c | 37 ++++- src/value.h | 12 +- 11 files changed, 419 insertions(+), 182 deletions(-) diff --git a/src/apfl.h b/src/apfl.h index ac234bd..f1e0ba4 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -722,6 +722,7 @@ struct apfl_messages { const char *io_error; const char *value_doesnt_match; const char *invalid_matcher_state; + const char *no_matching_subfunction; }; extern const struct apfl_messages apfl_messages; diff --git a/src/bytecode.c b/src/bytecode.c index 4996fae..580bf11 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -63,6 +63,7 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi case INSN_SET_LINE: case INSN_GET_BY_INDEX_KEEP: case INSN_MATCHER_SET_VAL: + case INSN_FUNC: i++; break; case INSN_STRING: @@ -75,7 +76,7 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi GET_ARGUMENT(ilist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); break; - case INSN_FUNC: + case INSN_FUNC_ADD_SUBFUNC: GET_ARGUMENT(ilist, i, arg); cb(opaque, GC_OBJECT_FROM(arg.body, GC_TYPE_INSTRUCTIONS)); break; @@ -143,6 +144,8 @@ apfl_instruction_to_string(enum instruction insn) 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_SET_VAL: @@ -208,8 +211,8 @@ apfl_matcher_instructions_deinit(struct apfl_allocator allocator, struct matcher FREE_LIST(allocator, milist->instructions, milist->cap); } -static bool -bytecode_dump_milist(unsigned indent, struct apfl_format_writer w, struct matcher_instruction_list *milist) +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++) { FMT_TRY(apfl_format_put_indent(w, indent)); @@ -249,8 +252,8 @@ bytecode_dump_milist(unsigned indent, struct apfl_format_writer w, struct matche arg = ilist->instructions[++i]; \ } while (0) -static bool -bytecode_dump_ilist(unsigned indent, struct apfl_format_writer w, struct instruction_list *ilist) +bool +apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruction_list *ilist) { union instruction_or_arg arg; @@ -280,6 +283,7 @@ bytecode_dump_ilist(unsigned indent, struct apfl_format_writer w, struct instruc break; case INSN_LIST: case INSN_SET_LINE: + case INSN_FUNC: GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); FMT_TRY(apfl_format_put_string(w, " ")); FMT_TRY(apfl_format_put_int(w, (int)arg.count)); @@ -301,17 +305,17 @@ bytecode_dump_ilist(unsigned indent, struct apfl_format_writer w, struct instruc FMT_TRY(apfl_format_put_string(w, " ")); FMT_TRY(apfl_format_put_string(w, *arg.string)); break; - case INSN_FUNC: + case INSN_FUNC_ADD_SUBFUNC: GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); FMT_TRY(apfl_format_put_string(w, " ilist{\n")); - FMT_TRY(bytecode_dump_ilist(indent+1, w, arg.body)); + FMT_TRY(apfl_bytecode_dump(indent+1, w, arg.body)); FMT_TRY(apfl_format_put_indent(w, indent)); FMT_TRY(apfl_format_put_string(w, "}")); break; case INSN_MATCHER_LOAD: GET_ARGUMENT_FOR_DUMP(w, ilist, i, arg); FMT_TRY(apfl_format_put_string(w, " milist{\n")); - FMT_TRY(bytecode_dump_milist(indent+1, w, arg.matcher)); + 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; @@ -331,9 +335,3 @@ bytecode_dump_ilist(unsigned indent, struct apfl_format_writer w, struct instruc return true; } - -bool -apfl_bytecode_dump(struct apfl_format_writer w, struct instruction_list *ilist) -{ - return bytecode_dump_ilist(0, w, ilist); -} diff --git a/src/bytecode.h b/src/bytecode.h index d4ee9f1..e4a1184 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -56,7 +56,8 @@ enum instruction { INSN_SET_LINE, // ( -- ), arg: count (new line number) INSN_DROP, // ( value -- ) INSN_CALL, // ( func list -- value ) - INSN_FUNC, // ( -- func ), arg: body + INSN_FUNC, // ( -- func ), arg: count + INSN_FUNC_ADD_SUBFUNC, // ( func -- func' ), arg: body INSN_MATCHER_LOAD, // ( -- ), arg: matcher INSN_MATCHER_SET_VAL, // ( val -- ), arg: index INSN_MATCHER_MUST_MATCH, // ( val -- ) @@ -94,7 +95,8 @@ void apfl_gc_instructions_traverse(struct 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 *); -bool apfl_bytecode_dump(struct apfl_format_writer, struct instruction_list *); +bool apfl_bytecode_dump_matcher(unsigned indent, struct apfl_format_writer w, struct matcher_instruction_list *milist); +bool apfl_bytecode_dump(unsigned indent, struct apfl_format_writer w, struct instruction_list *ilist); #ifdef __cplusplus } diff --git a/src/compile.c b/src/compile.c index 57a6255..86f9679 100644 --- a/src/compile.c +++ b/src/compile.c @@ -320,7 +320,7 @@ tmp_milist(struct compiler *compiler) return milist; } -struct compile_assignable_ilists { +struct compile_matchable_ilists { struct instruction_list *prelude; struct instruction_list *newvars; struct instruction_list *setvars; @@ -333,7 +333,7 @@ compile_assignable( bool local, struct apfl_position position, struct apfl_expr_assignable *assignable, - struct compile_assignable_ilists ilists + struct compile_matchable_ilists ilists ); static bool @@ -341,7 +341,7 @@ compile_assignable_var_or_member( struct compiler *compiler, bool local, struct apfl_expr_assignable_var_or_member *var_or_member, - struct compile_assignable_ilists ilists + struct compile_matchable_ilists ilists ) { size_t index = ilists.matcher->capture_count++; @@ -383,10 +383,10 @@ compile_assignable_var_or_member( } static bool -compile_assignable_constant( +compile_matchable_constant( struct compiler *compiler, struct apfl_expr_const *constant, - struct compile_assignable_ilists ilists + struct compile_matchable_ilists ilists ) { size_t index = ilists.matcher->value_count++; @@ -403,96 +403,121 @@ compile_assignable_constant( return true; } -static bool -compile_assignable_predicate( - struct compiler *compiler, - bool local, - struct apfl_position position, - struct apfl_expr_assignable_predicate *predicate, - struct compile_assignable_ilists ilists -) { - size_t index = ilists.matcher->value_count++; +#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); \ + } \ - 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); +DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( + compile_assignable_predicate, + compile_assignable, + struct apfl_expr_assignable_predicate +) - TRY(milist_ensure_cap(compiler, ilists.matcher, 2)); - milist_put_insn(ilists.matcher, MATCHER_CHECK_PRED); - milist_put_index(ilists.matcher, index); +#define DEF_COMPILE_ABSTRACT_MATCHABLE_LIST(name, compile_matchable, listtype, listmemb, listitemtype, listitemmemb) \ + static bool \ + name##_expand( \ + struct compiler *compiler, \ + bool local, \ + struct apfl_position position, \ + listtype *list, \ + struct compile_matchable_ilists ilists, \ + 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); \ + \ + for (size_t i = list->len; i-- > expand_at+1; ) { \ + listitemtype *item = &list->listmemb[i]; \ + \ + if (item->expand) { \ + compiler->error = (struct apfl_error) { \ + .type = APFL_ERR_ONLY_ONE_EXPAND_ALLOWED, \ + .position = position, \ + }; \ + return false; \ + } \ + \ + TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \ + } \ + \ + TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ + milist_put_insn(ilists.matcher, MATCHER_REMAINDING); \ + \ + TRY(compile_matchable(compiler, local, position, &expand_item->listitemmemb, ilists)); \ + \ + return true; \ + } \ + \ + static bool \ + name( \ + struct compiler *compiler, \ + bool local, \ + struct apfl_position position, \ + listtype *list, \ + struct compile_matchable_ilists ilists \ + ) { \ + TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ + milist_put_insn(ilists.matcher, MATCHER_ENTER_LIST); \ + \ + for (size_t i = 0; i < list->len; i++) { \ + listitemtype *item = &list->listmemb[i]; \ + \ + if (item->expand) { \ + return name##_expand( \ + compiler, \ + local, \ + position, \ + list, \ + ilists, \ + i \ + ); \ + } \ + \ + TRY(compile_matchable(compiler, local, position, &item->listitemmemb, ilists)); \ + } \ + \ + TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); \ + milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); \ + return true; \ + } \ - return compile_assignable(compiler, local, position, predicate->lhs, ilists); -} +DEF_COMPILE_ABSTRACT_MATCHABLE_LIST( + compile_assignable_list, + compile_assignable, + struct apfl_expr_assignable_list, + items, + struct apfl_expr_assignable_list_item, + assignable +) static bool -compile_assignable_list_expand( - struct compiler *compiler, - bool local, - struct apfl_position position, - struct apfl_expr_assignable_list *list, - struct compile_assignable_ilists ilists, - size_t expand_at -) { - assert(expand_at < list->len); - struct apfl_expr_assignable_list_item *expand_item = &list->items[expand_at]; - assert(expand_item->expand); - +compile_matchable_blank(struct compiler *compiler, struct compile_matchable_ilists ilists) +{ TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_CONTINUE_FROM_END); - - for (size_t i = list->len; i-- > expand_at+1; ) { - struct apfl_expr_assignable_list_item *item = &list->items[i]; - - if (item->expand) { - compiler->error = (struct apfl_error) { - .type = APFL_ERR_ONLY_ONE_EXPAND_ALLOWED, - .position = position, - }; - return false; - } - - TRY(compile_assignable(compiler, local, position, &item->assignable, ilists)); - } - - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_REMAINDING); - - TRY(compile_assignable(compiler, local, position, &expand_item->assignable, ilists)); - - return true; -} - -static bool -compile_assignable_list( - struct compiler *compiler, - bool local, - struct apfl_position position, - struct apfl_expr_assignable_list *list, - struct compile_assignable_ilists ilists -) { - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_ENTER_LIST); - - for (size_t i = 0; i < list->len; i++) { - struct apfl_expr_assignable_list_item *item = &list->items[i]; - - if (item->expand) { - return compile_assignable_list_expand( - compiler, - local, - position, - list, - ilists, - i - ); - } - - TRY(compile_assignable(compiler, local, position, &item->assignable, ilists)); - } - - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_LEAVE_LIST); + milist_put_insn(ilists.matcher, MATCHER_IGNORE); return true; } @@ -502,21 +527,19 @@ compile_assignable( bool local, struct apfl_position position, struct apfl_expr_assignable *assignable, - struct compile_assignable_ilists ilists + struct compile_matchable_ilists ilists ) { switch (assignable->type) { case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER: return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, ilists); case APFL_EXPR_ASSIGNABLE_CONSTANT: - return compile_assignable_constant(compiler, &assignable->constant, ilists); + return compile_matchable_constant(compiler, &assignable->constant, ilists); case APFL_EXPR_ASSIGNABLE_PREDICATE: return compile_assignable_predicate(compiler, local, position, &assignable->predicate, ilists); case APFL_EXPR_ASSIGNABLE_LIST: return compile_assignable_list(compiler, local, position, &assignable->list, ilists); case APFL_EXPR_ASSIGNABLE_BLANK: - TRY(milist_ensure_cap(compiler, ilists.matcher, 1)); - milist_put_insn(ilists.matcher, MATCHER_IGNORE); - return true; + return compile_matchable_blank(compiler, ilists); } assert(false); @@ -557,7 +580,7 @@ compile_complex_assignment( TRY(ilist_ensure_cap(compiler, ilist, 2)); - struct compile_assignable_ilists ilists = { + struct compile_matchable_ilists ilists = { .prelude = ilist, }; @@ -640,19 +663,116 @@ compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func struct instruction_list *body_ilist = NULL; MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line))); - // Drop the argument list, we ignore it in simple functions + struct matcher_instruction_list *milist = NULL; + MALLOC_FAIL_IF_NULL(compiler, (milist = tmp_milist(compiler))); + + 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_DROP); + ilist_put_insn(body_ilist, INSN_MATCHER_DROP); TRY(compile_body(compiler, func, body_ilist)); - TRY(ilist_ensure_cap(compiler, ilist, 2)); + 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_matcher(ilist, milist); + ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC); ilist_put_body(ilist, body_ilist); return true; } +static bool +compile_param( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_param *param, + struct compile_matchable_ilists ilists +); + +DEF_COMPILE_ABSTRACT_MATCHABLE_LIST( + compile_param_list, + compile_param, + struct apfl_expr_params, + params, + struct apfl_expr_params_item, + param +) + +DEF_COMPILE_ABSTRACT_MATCHABLE_PREDICATE( + compile_param_predicate, + compile_param, + struct apfl_expr_param_predicate +) + +static bool +compile_param_var( + struct compiler *compiler, + struct apfl_string *var, + struct compile_matchable_ilists ilists +) { + size_t index = ilists.matcher->capture_count++; + + /* 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 + * be pretty sweet if this would actually compare the value with the first + * capture, you could write == in apfl itself then: + * + * == := { + * x x -> true + * x y -> false + * } + */ + + 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)); + + 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); + + return true; +} + +static bool +compile_param( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_param *param, + struct compile_matchable_ilists ilists +) { + (void)local; + + switch (param->type) { + case APFL_EXPR_PARAM_VAR: + return compile_param_var(compiler, ¶m->var, ilists); + case APFL_EXPR_PARAM_CONSTANT: + return compile_matchable_constant(compiler, ¶m->constant, ilists); + case APFL_EXPR_PARAM_LIST: + return compile_param_list(compiler, true, position, ¶m->list, ilists); + case APFL_EXPR_PARAM_PREDICATE: + return compile_param_predicate(compiler, true, position, ¶m->predicate, ilists); + case APFL_EXPR_PARAM_BLANK: + return compile_matchable_blank(compiler, ilists); + } + + assert(false); + return false; +} + static bool compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line) { @@ -663,68 +783,55 @@ compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, stru } static bool -compile_complex_func_inner(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line) +compile_subfunc(struct compiler *compiler, struct apfl_expr_subfunc *subfunc, struct instruction_list *ilist, struct apfl_position position) { - if (func->len != 1) { - compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); - return false; - } - - struct apfl_expr_subfunc *subfunc = &func->subfuncs[0]; - struct instruction_list *body_ilist = NULL; - MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line))); + MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, position.line))); - for (size_t i = 0; i < subfunc->params.len; i++) { - struct apfl_expr_params_item *param = &subfunc->params.params[i]; - if (param->expand) { - compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); - return false; - } + struct matcher_instruction_list *matcher = NULL; + MALLOC_FAIL_IF_NULL(compiler, (matcher = tmp_milist(compiler))); - switch (param->param.type) { - case APFL_EXPR_PARAM_VAR: - { - TRY(ilist_ensure_cap(compiler, body_ilist, 4)); + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_MATCHER_LOAD); + ilist_put_matcher(ilist, matcher); - struct apfl_string *str; - TRY(string_move_into_new(compiler, &str, ¶m->param.var)); - - ilist_put_insn(body_ilist, INSN_GET_BY_INDEX_KEEP); - ilist_put_index(body_ilist, i); - ilist_put_insn(body_ilist, INSN_MOVE_TO_LOCAL_VAR); - ilist_put_string(body_ilist, str); - break; - } - case APFL_EXPR_PARAM_CONSTANT: - case APFL_EXPR_PARAM_PREDICATE: - case APFL_EXPR_PARAM_LIST: - compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); - return false; - case APFL_EXPR_PARAM_BLANK: - break; - } - } + TRY(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(ilist_ensure_cap(compiler, body_ilist, 1)); - ilist_put_insn(body_ilist, INSN_DROP); // Drop the argument list + ilist_put_insn(body_ilist, INSN_MATCHER_DROP); TRY(compile_body(compiler, &subfunc->body, body_ilist)); TRY(ilist_ensure_cap(compiler, ilist, 2)); - ilist_put_insn(ilist, INSN_FUNC); + ilist_put_insn(ilist, INSN_FUNC_ADD_SUBFUNC); ilist_put_body(ilist, body_ilist); return true; } static bool -compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line) +compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, struct apfl_position position) { - size_t tmproots = apfl_gc_tmproots_begin(compiler->gc); - bool ok = compile_complex_func_inner(compiler, func, ilist, line); - apfl_gc_tmproots_restore(compiler->gc, tmproots); - return ok; + TRY(ilist_ensure_cap(compiler, ilist, 2)); + ilist_put_insn(ilist, INSN_FUNC); + ilist_put_count(ilist, func->len); + + for (size_t i = 0; i < func->len; i++) { + struct apfl_expr_subfunc *subfunc = &func->subfuncs[i]; + + size_t tmproots = apfl_gc_tmproots_begin(compiler->gc); + bool ok = compile_subfunc(compiler, subfunc, ilist, position); + apfl_gc_tmproots_restore(compiler->gc, tmproots); + + TRY(ok); + } + + return true; } static bool @@ -750,7 +857,7 @@ compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instructi case APFL_EXPR_SIMPLE_FUNC: return compile_simple_func(compiler, &expr->simple_func, ilist, new_line); case APFL_EXPR_COMPLEX_FUNC: - return compile_complex_func(compiler, &expr->complex_func, ilist, new_line); + return compile_complex_func(compiler, &expr->complex_func, ilist, expr->position); case APFL_EXPR_CONSTANT: return compile_constant(compiler, &expr->constant, ilist); case APFL_EXPR_BLANK: diff --git a/src/context.c b/src/context.c index fab0752..f28c200 100644 --- a/src/context.c +++ b/src/context.c @@ -479,6 +479,12 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER) ); break; + case CSE_FUNCTION_DISPATCH: + visitor( + opaque, + GC_OBJECT_FROM(cse.func_dispatch.function, GC_TYPE_FUNC) + ); + break; } } @@ -528,6 +534,7 @@ apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_ switch (entry->type) { case CSE_FUNCTION: case CSE_CFUNCTION: + case CSE_FUNCTION_DISPATCH: break; case CSE_MATCHER: FREE_LIST(allocator, entry->matcher.matcher_stack, entry->matcher.matcher_stack_cap); diff --git a/src/context.h b/src/context.h index 70dfdf2..7329bbf 100644 --- a/src/context.h +++ b/src/context.h @@ -27,6 +27,7 @@ enum call_stack_entry_type { CSE_FUNCTION, CSE_CFUNCTION, CSE_MATCHER, + CSE_FUNCTION_DISPATCH, }; struct func_call_stack_entry { @@ -72,6 +73,12 @@ struct matcher_call_stack_entry { size_t matcher_stack_cap; }; +struct func_dispatch_call_stack_entry { + size_t subfunc; + bool returning_from_matcher; + struct function *function; +}; + struct call_stack_entry { enum call_stack_entry_type type; @@ -81,6 +88,7 @@ struct call_stack_entry { struct func_call_stack_entry func; struct cfunc_call_stack_entry cfunc; struct matcher_call_stack_entry matcher; + struct func_dispatch_call_stack_entry func_dispatch; }; }; diff --git a/src/eval.c b/src/eval.c index fc23a2f..5fde49a 100644 --- a/src/eval.c +++ b/src/eval.c @@ -15,6 +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 @@ -238,7 +239,7 @@ variable_new(apfl_ctx ctx, struct apfl_string *name, bool local) } static void -func_inner(apfl_ctx ctx, struct instruction_list *body) +func_inner(apfl_ctx ctx, size_t count) { struct scope *scope = apfl_closure_scope_for_func(ctx); if (scope == NULL) { @@ -254,7 +255,7 @@ func_inner(apfl_ctx ctx, struct instruction_list *body) apfl_raise_alloc_error(ctx); } - if ((func_value->func = apfl_func_new(&ctx->gc, body, scope)) == NULL) { + if ((func_value->func = apfl_func_new(&ctx->gc, count, scope)) == NULL) { stack_must_drop(ctx, -1); apfl_raise_alloc_error(ctx); } @@ -263,13 +264,34 @@ func_inner(apfl_ctx ctx, struct instruction_list *body) } static void -func(apfl_ctx ctx, struct instruction_list *body) +func(apfl_ctx ctx, size_t count) { size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - func_inner(ctx, body); + func_inner(ctx, count); apfl_gc_tmproots_restore(&ctx->gc, tmproots); } +static void +func_add_subfunc(apfl_ctx ctx, struct func_call_stack_entry *cse, struct instruction_list *body) +{ + // TODO: Better error messsages + + struct apfl_value value = apfl_stack_must_get(ctx, -1); + if (value.type != VALUE_FUNC) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } + + if (cse->matcher == NULL) { + 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; +} + static void call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) { @@ -365,6 +387,9 @@ evaluate_until_call_stack_return(apfl_ctx ctx) case CSE_MATCHER: evaluate_matcher(ctx, &cse->matcher); break; + case CSE_FUNCTION_DISPATCH: + dispatch(ctx, cse); + break; } } } @@ -402,16 +427,12 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid switch (func.type) { case VALUE_FUNC: prepare_call(ctx, tmproots, args, (struct call_stack_entry) { - .type = CSE_FUNCTION, + .type = CSE_FUNCTION_DISPATCH, .stack = apfl_stack_new(), - .func = { - .pc = 0, - .instructions = func.func->body, - .scope = NULL, - .closure_scope = func.func->scope, - .execution_line = func.func->body->line, + .func_dispatch = { + .subfunc = 0, + .function = func.func, .returning_from_matcher = false, - .matcher = NULL, }, }); @@ -622,7 +643,11 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) return; case INSN_FUNC: must_get_argument(ctx, pc, ilist, &arg); - func(ctx, arg.body); + func(ctx, 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: must_get_argument(ctx, pc, ilist, &arg); @@ -649,7 +674,7 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) 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: + 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); @@ -965,7 +990,7 @@ return_from_matcher(apfl_ctx ctx, bool result) cse = apfl_call_stack_cur_entry(ctx); assert(cse != NULL); - assert(cse->type == CSE_FUNCTION); + assert(cse->type == CSE_FUNCTION || cse->type == CSE_FUNCTION_DISPATCH); } #define RETURN_WITHOUT_MATCH(ctx) \ @@ -1050,6 +1075,45 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) ); } +static void +dispatch(apfl_ctx ctx, struct call_stack_entry *cse) +{ + assert(cse->type == CSE_FUNCTION_DISPATCH); + struct func_dispatch_call_stack_entry *fd_cse = &cse->func_dispatch; + + struct function *function = fd_cse->function; + + if (fd_cse->returning_from_matcher) { + struct subfunction *subfunction = &function->subfunctions[fd_cse->subfunc]; + if (subfunction->matcher->result) { + // 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, + .execution_line = subfunction->body->line, + .matcher = subfunction->matcher, + .returning_from_matcher = false, + }; + + return; + } + + fd_cse->subfunc++; + fd_cse->returning_from_matcher = false; + } + + if (fd_cse->subfunc >= function->subfunctions_len) { + 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); +} + struct apfl_iterative_runner_data { apfl_ctx ctx; apfl_tokenizer_ptr tokenizer; diff --git a/src/globals.c b/src/globals.c index b696988..aad8b94 100644 --- a/src/globals.c +++ b/src/globals.c @@ -187,7 +187,21 @@ disasm(apfl_ctx ctx) apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type); } - TRY_FORMAT(ctx, apfl_bytecode_dump(w, value.func->body)); + struct function *func = value.func; + + for (size_t i = 0; i < func->subfunctions_len; i++) { + struct subfunction *subfunction = &func->subfunctions[i]; + + TRY_FORMAT(ctx, apfl_format_put_string(w, "Subfunction #")); + TRY_FORMAT(ctx, apfl_format_put_number(w, (int)i)); + TRY_FORMAT(ctx, apfl_format_put_string(w, "\n")); + TRY_FORMAT(ctx, apfl_format_put_indent(w, 1)); + TRY_FORMAT(ctx, apfl_format_put_string(w, "Matcher\n")); + TRY_FORMAT(ctx, apfl_bytecode_dump_matcher(2, w, subfunction->matcher->instructions)); + TRY_FORMAT(ctx, apfl_format_put_indent(w, 1)); + TRY_FORMAT(ctx, apfl_format_put_string(w, "Instructions\n")); + TRY_FORMAT(ctx, apfl_bytecode_dump(2, w, subfunction->body)); + } } static const struct global_def globals[] = { diff --git a/src/messages.c b/src/messages.c index 0e57e41..bdb4e9d 100644 --- a/src/messages.c +++ b/src/messages.c @@ -20,4 +20,5 @@ const struct apfl_messages apfl_messages = { .io_error = "I/O error", .value_doesnt_match = "Value does not match", .invalid_matcher_state = "Matcher is in invalid state", + .no_matching_subfunction = "No matching subfunction", }; diff --git a/src/value.c b/src/value.c index 10c74ee..2841b20 100644 --- a/src/value.c +++ b/src/value.c @@ -188,26 +188,49 @@ apfl_value_type_to_abstract_type(enum value_type type) } struct function * -apfl_func_new(struct gc *gc, struct instruction_list *body, struct scope *scope) +apfl_func_new(struct gc *gc, size_t cap, struct scope *scope) { + struct subfunction *subfunctions = ALLOC_LIST(gc->allocator, struct subfunction, cap); + if (subfunctions == NULL) { + return NULL; + } + struct function *function = apfl_gc_new_func(gc); if (function == NULL) { return NULL; } *function = (struct function) { - .body = body, + .subfunctions = subfunctions, + .subfunctions_len = 0, + .subfunctions_cap = cap, .scope = scope, }; return function; } +bool +apfl_func_add_subfunc(struct function *function, struct instruction_list *body, struct matcher *matcher) +{ + if (function->subfunctions_len >= function->subfunctions_cap) { + return false; + } + + function->subfunctions[function->subfunctions_len] = (struct subfunction) { + .body = body, + .matcher = matcher, + }; + + function->subfunctions_len++; + + return true; +} + void apfl_function_deinit(struct apfl_allocator allocator, struct function *function) { - (void)allocator; - (void)function; + FREE_LIST(allocator, function->subfunctions, function->subfunctions_cap); } struct cfunction * @@ -666,7 +689,11 @@ apfl_gc_dict_traverse(struct dict_header *dict, gc_visitor cb, void *opaque) void apfl_gc_func_traverse(struct function* function, gc_visitor cb, void *opaque) { - cb(opaque, GC_OBJECT_FROM(function->body, GC_TYPE_INSTRUCTIONS)); + for (size_t i = 0; i < function->subfunctions_len; i++) { + struct subfunction *sub = &function->subfunctions[i]; + cb(opaque, GC_OBJECT_FROM(sub->body, GC_TYPE_INSTRUCTIONS)); + cb(opaque, GC_OBJECT_FROM(sub->matcher, GC_TYPE_MATCHER)); + } cb(opaque, GC_OBJECT_FROM(function->scope, GC_TYPE_SCOPE)); } diff --git a/src/value.h b/src/value.h index 364a0b5..98a2d89 100644 --- a/src/value.h +++ b/src/value.h @@ -41,8 +41,15 @@ struct dict_header { bool copy_on_write; }; -struct function { +struct subfunction { + struct matcher *matcher; struct instruction_list *body; +}; + +struct function { + struct subfunction *subfunctions; + size_t subfunctions_len; + size_t subfunctions_cap; struct scope *scope; }; @@ -116,7 +123,8 @@ bool apfl_dict_set_raw( size_t apfl_dict_len(struct dict_header *); void apfl_dict_deinit(struct dict_header *); -struct function *apfl_func_new(struct gc *, struct instruction_list *, struct scope *); +struct function *apfl_func_new(struct gc *, size_t cap, struct scope *); +bool apfl_func_add_subfunc(struct function *, struct instruction_list *, struct matcher *); void apfl_function_deinit(struct apfl_allocator, struct function *); struct cfunction *apfl_cfunc_new(struct gc *, apfl_cfunc, size_t nslots);