diff --git a/src/Makefile.am b/src/Makefile.am index 2af7b05..2215616 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ libapfl_a_SOURCES += format.c libapfl_a_SOURCES += gc.c libapfl_a_SOURCES += hashmap.c libapfl_a_SOURCES += globals.c +libapfl_a_SOURCES += matcher.c libapfl_a_SOURCES += messages.c libapfl_a_SOURCES += parser.c libapfl_a_SOURCES += position.c @@ -34,6 +35,7 @@ apfl_internal_headers += format.h apfl_internal_headers += gc.h apfl_internal_headers += globals.h apfl_internal_headers += hashmap.h +apfl_internal_headers += matcher.h apfl_internal_headers += resizable.h apfl_internal_headers += scope.h apfl_internal_headers += strings.h diff --git a/src/apfl.h b/src/apfl.h index 48de64e..ac234bd 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -720,6 +720,8 @@ struct apfl_messages { const char *not_a_function; const char *wrong_type; const char *io_error; + const char *value_doesnt_match; + const char *invalid_matcher_state; }; extern const struct apfl_messages apfl_messages; diff --git a/src/bytecode.c b/src/bytecode.c index 6446655..4654185 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -54,11 +54,14 @@ apfl_gc_instructions_traverse(struct instruction_list *ilist, gc_visitor cb, voi case INSN_NEXT_LINE: case INSN_DROP: case INSN_CALL: + case INSN_MATCHER_MUST_MATCH: + case INSN_MATCHER_DROP: break; case INSN_NUMBER: case INSN_LIST: case INSN_SET_LINE: case INSN_GET_BY_INDEX_KEEP: + case INSN_MATCHER_SET_VAL: i++; break; case INSN_STRING: @@ -75,6 +78,16 @@ 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: + 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); + cb(opaque, GC_OBJECT_FROM(arg.string, GC_TYPE_STRING)); + i++; + break; } } } @@ -129,7 +142,67 @@ apfl_instruction_to_string(enum instruction insn) return "INSN_CALL"; case INSN_FUNC: return "INSN_FUNC"; + case INSN_MATCHER_LOAD: + return "INSN_MATCHER_LOAD"; + 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 "??"; } + +const char * +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_CHECK_CONST: + return "MATCHER_CHECK_CONST"; + case MATCHER_CHECK_PRED: + return "MATCHER_CHECK_PRED"; + case MATCHER_ENTER_LIST: + return "MATCHER_ENTER_LIST"; + case MATCHER_LEAVE_LIST: + return "MATCHER_LEAVE_LIST"; + case MATCHER_CONTINUE_FROM_END: + return "MATCHER_CONTINUE_FROM_END"; + case MATCHER_REMAINDING: + return "MATCHER_REMAINDING"; + } + + return "??"; +} + +struct matcher_instruction_list * +apfl_matcher_instructions_new(struct gc *gc) +{ + struct matcher_instruction_list *milist = apfl_gc_new_matcher_instructions(gc); + if (milist == NULL) { + return NULL; + } + *milist = (struct matcher_instruction_list) { + .instructions = NULL, + .len = 0, + .cap = 0, + .capture_count = 0, + .value_count = 0, + }; + return milist; +} + +void +apfl_matcher_instructions_deinit(struct apfl_allocator allocator, struct matcher_instruction_list *milist) +{ + FREE_LIST(allocator, milist->instructions, milist->cap); +} diff --git a/src/bytecode.h b/src/bytecode.h index 57e7a86..7092944 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -9,30 +9,60 @@ extern "C" { #include "gc.h" +enum matcher_instruction { + MATCHER_IGNORE, + MATCHER_CAPTURE, // with index as capture index + MATCHER_CHECK_CONST, // with index as values index + MATCHER_CHECK_PRED, // with index as values index + MATCHER_ENTER_LIST, + MATCHER_LEAVE_LIST, + MATCHER_CONTINUE_FROM_END, + MATCHER_REMAINDING, +}; + +union matcher_instruction_or_arg { + enum matcher_instruction instruction; + size_t index; +}; + +struct matcher_instruction_list { + union matcher_instruction_or_arg *instructions; + size_t len; + size_t cap; + size_t capture_count; + size_t value_count; +}; + enum instruction { - INSN_NIL, // ( -- nil) - INSN_TRUE, // ( -- true) - INSN_FALSE, // ( -- false) - INSN_NUMBER, // ( -- number), arg: number - INSN_STRING, // ( -- string), arg: string - INSN_LIST, // ( -- list), arg: count (preallocation hint) - INSN_LIST_APPEND, // ( list val -- list' ) - INSN_LIST_EXPAND_INTO, // ( list list -- list' ) - INSN_DICT, // ( -- dict ) - INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' ) - INSN_GET_MEMBER, // ( list/dict key -- value ) - INSN_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index - INSN_VAR_GET, // ( -- value ), arg: string - INSN_VAR_SET, // ( value -- value ), arg: string - INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string - INSN_VAR_NEW, // ( -- ), arg: string - INSN_VAR_NEW_LOCAL, // ( -- ), arg: string - INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string - INSN_NEXT_LINE, // ( -- ) - INSN_SET_LINE, // ( -- ), arg: count (new line number) - INSN_DROP, // ( value -- ) - INSN_CALL, // ( func list -- value ) - INSN_FUNC, // ( -- func ), arg: body + INSN_NIL, // ( -- nil) + INSN_TRUE, // ( -- true) + INSN_FALSE, // ( -- false) + INSN_NUMBER, // ( -- number), arg: number + INSN_STRING, // ( -- string), arg: string + INSN_LIST, // ( -- list), arg: count (preallocation hint) + INSN_LIST_APPEND, // ( list val -- list' ) + INSN_LIST_EXPAND_INTO, // ( list list -- list' ) + INSN_DICT, // ( -- dict ) + INSN_DICT_APPEND_KVPAIR, // ( dict key value -- dict' ) + INSN_GET_MEMBER, // ( list/dict key -- value ) + INSN_GET_BY_INDEX_KEEP, // ( list/dict -- list/dict value ), arg: index + INSN_VAR_GET, // ( -- value ), arg: string + INSN_VAR_SET, // ( value -- value ), arg: string + INSN_VAR_SET_LOCAL, // ( value -- value ), arg: string + INSN_VAR_NEW, // ( -- ), arg: string + INSN_VAR_NEW_LOCAL, // ( -- ), arg: string + INSN_MOVE_TO_LOCAL_VAR, // ( value -- ), arg: string + INSN_NEXT_LINE, // ( -- ) + INSN_SET_LINE, // ( -- ), arg: count (new line number) + INSN_DROP, // ( value -- ) + INSN_CALL, // ( func list -- value ) + INSN_FUNC, // ( -- func ), arg: body + INSN_MATCHER_LOAD, // ( -- ), arg: matcher + 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 }; union instruction_or_arg { @@ -42,6 +72,7 @@ union instruction_or_arg { size_t count; size_t index; struct instruction_list *body; + struct matcher_instruction_list *matcher; }; struct instruction_list { @@ -53,12 +84,16 @@ struct instruction_list { }; const char *apfl_instruction_to_string(enum instruction); +const char *apfl_matcher_instruction_to_string(enum matcher_instruction); struct instruction_list *apfl_instructions_new(struct gc *, int line); void apfl_instructions_deinit(struct apfl_allocator, struct instruction_list *); void apfl_gc_instructions_traverse(struct instruction_list *, gc_visitor, void *); +struct matcher_instruction_list *apfl_matcher_instructions_new(struct gc *); +void apfl_matcher_instructions_deinit(struct apfl_allocator, struct matcher_instruction_list *); + #ifdef __cplusplus } #endif diff --git a/src/compile.c b/src/compile.c index 3605bd1..ce2d5c8 100644 --- a/src/compile.c +++ b/src/compile.c @@ -103,6 +103,53 @@ ilist_put_index(struct instruction_list *ilist, size_t index) ILIST_PUT(ilist, index, index, "put_index: %d", (int)index) } +static void +ilist_put_matcher(struct instruction_list *ilist, struct matcher_instruction_list *milist) +{ + ILIST_PUT(ilist, matcher, milist, "put_matcher: %p", (void *)milist) +} + +static bool +milist_ensure_cap(struct compiler *compiler, struct matcher_instruction_list *milist, size_t n) +{ + return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements( + compiler->gc->allocator, + sizeof(union matcher_instruction_or_arg), + (void **)&milist->instructions, + milist->len, + &milist->cap, + n + )); +} + +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++; + + DEBUG_LOG("milist %p: put_insn: %s\n", (void *)milist, apfl_matcher_instruction_to_string(instruction)); +} + +static void +milist_put_index(struct matcher_instruction_list *milist, size_t index) +{ + assert(milist->cap > 0); + assert(milist->len < milist->cap); + + milist->instructions[milist->len] = (union matcher_instruction_or_arg) { + .index = index, + }; + milist->len++; + + DEBUG_LOG("milist %p: put_index: %d\n", (void *)milist, (int)index); +} + static bool string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in) { @@ -270,10 +317,276 @@ tmp_ilist(struct compiler *compiler, int line) return ilist; } +struct compile_assignable_ilists { + struct instruction_list *prelude; + struct instruction_list *newvars; + struct instruction_list *setvars; +}; + +static bool +compile_assignable( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_assignable *assignable, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +); + +static bool +compile_assignable_var_or_member( + struct compiler *compiler, + bool local, + struct apfl_expr_assignable_var_or_member *var_or_member, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +) { + size_t index = milist->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 + * 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, milist, 2)); + milist_put_insn(milist, MATCHER_CAPTURE); + milist_put_index(milist, index); + + TRY(ilist_ensure_cap(compiler, ilists.newvars, 2)); + + 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(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); + + return true; +} + +static bool +compile_assignable_constant( + struct compiler *compiler, + struct apfl_expr_const *constant, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +) { + size_t index = 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(milist_ensure_cap(compiler, milist, 3)); + milist_put_insn(milist, MATCHER_CHECK_CONST); + milist_put_index(milist, index); + milist_put_insn(milist, MATCHER_IGNORE); + + return true; +} + +static bool +compile_assignable_predicate( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_assignable_predicate *predicate, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +) { + size_t index = milist->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, milist, 2)); + milist_put_insn(milist, MATCHER_CHECK_PRED); + milist_put_index(milist, index); + + return compile_assignable(compiler, local, position, predicate->lhs, milist, ilists); +} + +static bool +compile_assignable_list_expand( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_assignable_list *list, + struct matcher_instruction_list *milist, + 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); + + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, 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, milist, ilists)); + } + + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, MATCHER_REMAINDING); + + TRY(compile_assignable(compiler, local, position, &expand_item->assignable, milist, ilists)); + + return true; +} + +static bool +compile_assignable_list( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_assignable_list *list, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +) { + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, 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, + milist, + ilists, + i + ); + } + + TRY(compile_assignable(compiler, local, position, &item->assignable, milist, ilists)); + } + + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, MATCHER_LEAVE_LIST); + return true; +} + +static bool +compile_assignable( + struct compiler *compiler, + bool local, + struct apfl_position position, + struct apfl_expr_assignable *assignable, + struct matcher_instruction_list *milist, + struct compile_assignable_ilists ilists +) { + switch (assignable->type) { + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER: + return compile_assignable_var_or_member(compiler, local, &assignable->var_or_member, milist, ilists); + case APFL_EXPR_ASSIGNABLE_CONSTANT: + return compile_assignable_constant(compiler, &assignable->constant, milist, ilists); + case APFL_EXPR_ASSIGNABLE_PREDICATE: + return compile_assignable_predicate(compiler, local, position, &assignable->predicate, milist, ilists); + case APFL_EXPR_ASSIGNABLE_LIST: + return compile_assignable_list(compiler, local, position, &assignable->list, milist, ilists); + case APFL_EXPR_ASSIGNABLE_BLANK: + TRY(milist_ensure_cap(compiler, milist, 1)); + milist_put_insn(milist, MATCHER_IGNORE); + return true; + } + + assert(false); + return false; +} + +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; +} + +static bool +compile_complex_assignment( + struct compiler *compiler, + struct apfl_expr_assignment *assignment, + struct apfl_position position, + struct instruction_list *ilist +) { + TRY(ilist_ensure_cap(compiler, ilist, 2)); + + struct matcher_instruction_list *milist; + if ((milist = apfl_matcher_instructions_new(compiler->gc)) == NULL) { + compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + return false; + } + + ilist_put_insn(ilist, INSN_MATCHER_LOAD); + ilist_put_matcher(ilist, milist); + + struct compile_assignable_ilists ilists = { + .prelude = ilist, + }; + + MALLOC_FAIL_IF_NULL(compiler, (ilists.newvars = tmp_ilist(compiler, position.line))); + MALLOC_FAIL_IF_NULL(compiler, (ilists.setvars = tmp_ilist(compiler, position.line))); + + TRY(compile_assignable(compiler, assignment->local, position, &assignment->lhs, milist, ilists)); + + TRY(concat_ilists(compiler, ilist, ilists.newvars)); + + TRY(compile_expr(compiler, assignment->rhs, ilist)); + + TRY(ilist_ensure_cap(compiler, ilist, 1)); + 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; +} + static bool compile_assignment( struct compiler *compiler, struct apfl_expr_assignment *assignment, + struct apfl_position position, struct instruction_list *ilist ) { if ( @@ -290,7 +603,12 @@ compile_assignment( ); } - // TODO: Implement other assignables + size_t tmproots = apfl_gc_tmproots_begin(compiler->gc); + bool ok = compile_complex_assignment(compiler, assignment, position, ilist); + apfl_gc_tmproots_restore(compiler->gc, tmproots); + + return ok; + compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED); return false; } @@ -449,7 +767,7 @@ compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instructi case APFL_EXPR_VAR: return compile_var(compiler, &expr->var, ilist); case APFL_EXPR_ASSIGNMENT: - return compile_assignment(compiler, &expr->assignment, ilist); + return compile_assignment(compiler, &expr->assignment, expr->position, ilist); } assert(false); diff --git a/src/context.c b/src/context.c index c8c11ae..13618d2 100644 --- a/src/context.c +++ b/src/context.c @@ -466,6 +466,12 @@ gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, vo GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC) ); break; + case CSE_MATCHER: + visitor( + opaque, + GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER) + ); + break; } } @@ -511,6 +517,15 @@ void apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry) { deinit_stack(allocator, &entry->stack); + + switch (entry->type) { + case CSE_FUNCTION: + case CSE_CFUNCTION: + break; + case CSE_MATCHER: + FREE_LIST(allocator, entry->matcher.matcher_stack, entry->matcher.matcher_stack_cap); + break; + } } struct call_stack_entry * diff --git a/src/context.h b/src/context.h index 8fdbbfa..70dfdf2 100644 --- a/src/context.h +++ b/src/context.h @@ -11,6 +11,7 @@ extern "C" { #include "bytecode.h" #include "hashmap.h" #include "gc.h" +#include "matcher.h" #include "value.h" #include "scope.h" @@ -25,6 +26,7 @@ struct stack { enum call_stack_entry_type { CSE_FUNCTION, CSE_CFUNCTION, + CSE_MATCHER, }; struct func_call_stack_entry { @@ -36,12 +38,40 @@ struct func_call_stack_entry { struct scope *closure_scope; int execution_line; + + struct matcher *matcher; + bool returning_from_matcher; }; struct cfunc_call_stack_entry { struct cfunction *func; }; +enum matcher_mode { + MATCHER_MODE_VALUE, + MATCHER_MODE_STOP, + MATCHER_MODE_LIST_START, + MATCHER_MODE_LIST_END, + MATCHER_MODE_LIST_REMAINING, + MATCHER_MODE_LIST_UNDERFLOW, +}; + +struct matcher_stack_entry { + enum matcher_mode mode; + + size_t lower; + size_t upper; +}; + +struct matcher_call_stack_entry { + size_t pc; + struct matcher *matcher; + + struct matcher_stack_entry *matcher_stack; + size_t matcher_stack_len; + size_t matcher_stack_cap; +}; + struct call_stack_entry { enum call_stack_entry_type type; @@ -50,6 +80,7 @@ struct call_stack_entry { union { struct func_call_stack_entry func; struct cfunc_call_stack_entry cfunc; + struct matcher_call_stack_entry matcher; }; }; diff --git a/src/eval.c b/src/eval.c index fe94a52..57742c0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -8,11 +8,14 @@ #include "context.h" #include "format.h" #include "hashmap.h" +#include "matcher.h" #include "resizable.h" #include "strings.h" #include "value.h" 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 matcher_init_matching(apfl_ctx ctx, struct matcher *matcher); static void stack_must_drop(apfl_ctx ctx, apfl_stackidx index) @@ -20,23 +23,41 @@ stack_must_drop(apfl_ctx ctx, apfl_stackidx index) assert(apfl_stack_drop(ctx, index)); } +#define ABSTRACT_GET_ARGUMENT(i, ilist, arg) \ + if (*i >= ilist->len) { \ + return false; \ + } \ + \ + *arg = ilist->instructions[(*i)++]; \ + return true; + +#define ABSTRACT_MUST_GET_ARG(get, ctx, i, ilist, arg) \ + if (!get(i, ilist, arg)) { \ + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); \ + } + static bool get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg) { - if (*i >= ilist->len) { - return false; - } - - *arg = ilist->instructions[(*i)++]; - return true; + ABSTRACT_GET_ARGUMENT(i, ilist, arg) } static void must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg) { - if (!get_argument(i, ilist, arg)) { - apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); - } + ABSTRACT_MUST_GET_ARG(get_argument, ctx, i, ilist, arg) +} + +static bool +get_matcher_argument(size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg) +{ + ABSTRACT_GET_ARGUMENT(i, milist, arg) +} + +static void +must_get_matcher_argument(apfl_ctx ctx, size_t *i, struct matcher_instruction_list *milist, union matcher_instruction_or_arg *arg) +{ + ABSTRACT_MUST_GET_ARG(get_matcher_argument, ctx, i, milist, arg) } static struct func_call_stack_entry * @@ -149,10 +170,8 @@ try_variable_update_existing_for_scope_type( } static void -variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local) +variable_set_value(apfl_ctx ctx, struct apfl_string *name, bool local, struct apfl_value value) { - struct apfl_value value = apfl_stack_must_get(ctx, -1); - bool was_set = false; if (!local) { was_set = try_variable_update_existing_for_scope_type(ctx, name, value, SCOPE_LOCAL) @@ -167,6 +186,14 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool lo apfl_raise_alloc_error(ctx); } } +} + +static void +variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack, bool local) +{ + struct apfl_value value = apfl_stack_must_get(ctx, -1); + + variable_set_value(ctx, name, local, value); if (keep_on_stack) { // If the value should be kept on the stack, the value is now in two @@ -261,14 +288,8 @@ call_stack_push(apfl_ctx ctx, struct call_stack_entry cse) } static void -return_from_function_inner(apfl_ctx ctx) +call_stack_drop(apfl_ctx ctx) { - struct apfl_value value; - if (!apfl_stack_pop(ctx, &value, -1)) { - // No return value on the stack. Return nil instead - value = (struct apfl_value) { .type = VALUE_NIL }; - } - assert(ctx->call_stack.len > 0); apfl_call_stack_entry_deinit(ctx->gc.allocator, apfl_call_stack_cur_entry(ctx)); @@ -284,6 +305,18 @@ return_from_function_inner(apfl_ctx ctx) ctx->call_stack.len - 1 ) ); +} + +static void +return_from_function_inner(apfl_ctx ctx) +{ + struct apfl_value value; + if (!apfl_stack_pop(ctx, &value, -1)) { + // No return value on the stack. Return nil instead + value = (struct apfl_value) { .type = VALUE_NIL }; + } + + call_stack_drop(ctx); apfl_stack_must_push(ctx, value); } @@ -309,7 +342,7 @@ prepare_call(apfl_ctx ctx, size_t tmproots, struct apfl_value args, struct call_ } // Keep evaluate instructions until we've returned from the current call stack. -// Must only be called with a CSE_FUNCTION on top of the call stack. +// Must not be called with a CSE_CFUNCTION on top of the call stack. static void evaluate_until_call_stack_return(apfl_ctx ctx) { @@ -321,9 +354,18 @@ evaluate_until_call_stack_return(apfl_ctx ctx) while (call_stack->len >= depth_started) { struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); assert(cse != NULL); - assert(cse->type == CSE_FUNCTION); - evaluate(ctx, &cse->func); + switch (cse->type) { + case CSE_CFUNCTION: + assert(false); + break; + case CSE_FUNCTION: + evaluate(ctx, &cse->func); + break; + case CSE_MATCHER: + evaluate_matcher(ctx, &cse->matcher); + break; + } } } @@ -368,6 +410,8 @@ call_inner(apfl_ctx ctx, size_t tmproots, apfl_stackidx func_index, apfl_stackid .scope = NULL, .closure_scope = func.func->scope, .execution_line = func.func->body->line, + .returning_from_matcher = false, + .matcher = NULL, }, }); @@ -410,10 +454,80 @@ 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) +{ + 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); + } + + if (index >= matcher->instructions->value_count) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } + + matcher->values[index] = apfl_stack_must_pop(ctx, -1); +} + +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 +) { + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + variable_set_from_matcher_inner(ctx, matcher, varname, index, local); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); +} + 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; + 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; @@ -510,6 +624,36 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) must_get_argument(ctx, pc, ilist, &arg); func(ctx, arg.body); goto continue_loop; + case INSN_MATCHER_LOAD: + must_get_argument(ctx, pc, ilist, &arg); + matcher_load(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); + 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 + // So this new CSE gets executed. By setting returning_from_matcher, we know that we came from the matcher, + // once it returns. + + matcher_init_matching(ctx, cse->matcher); + cse->returning_from_matcher = true; + + 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); @@ -520,6 +664,388 @@ evaluate(apfl_ctx ctx, struct func_call_stack_entry *cse) return_from_function(ctx); } +static void +matcher_stack_push(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct matcher_stack_entry entry) +{ + if (!apfl_resizable_append( + ctx->gc.allocator, + sizeof(struct matcher_stack_entry), + (void **)&cse->matcher_stack, + &cse->matcher_stack_len, + &cse->matcher_stack_cap, + &entry, + 1 + )) { + apfl_raise_alloc_error(ctx); + } +} + +APFL_NORETURN static void +raise_invalid_matcher_state(apfl_ctx ctx) +{ + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_matcher_state); +} + +static void +matcher_stack_drop(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + if (cse->matcher_stack_len == 0) { + raise_invalid_matcher_state(ctx); + } + + assert( + // We're shrinking, should not fail + apfl_resizable_resize( + ctx->gc.allocator, + sizeof(struct matcher_stack_entry), + (void **)&cse->matcher_stack, + &cse->matcher_stack_len, + &cse->matcher_stack_cap, + cse->matcher_stack_len-1 + ) + ); +} + +static void +matcher_init_matching_inner(apfl_ctx ctx, struct matcher *matcher) +{ + struct apfl_value value = apfl_stack_must_get(ctx, -1); + must_tmproot_add_value(ctx, value); + + struct matcher_call_stack_entry matcher_cse = { + .pc = 0, + .matcher = matcher, + .matcher_stack = NULL, + .matcher_stack_len = 0, + .matcher_stack_cap = 0, + }; + + matcher_stack_push(ctx, &matcher_cse, (struct matcher_stack_entry) { + .mode = MATCHER_MODE_VALUE, + }); + + call_stack_push(ctx, (struct call_stack_entry) { + .type = CSE_MATCHER, + .stack = apfl_stack_new(), + .matcher = matcher_cse, + }); + + apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value)); +} + +static void +matcher_init_matching(apfl_ctx ctx, struct matcher *matcher) +{ + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + matcher_init_matching_inner(ctx, matcher); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); +} + +static void +matcher_check_index(apfl_ctx ctx, size_t count, size_t index) +{ + if (index >= count) { + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode); + } +} + +static struct matcher_stack_entry * +matcher_cur_stack_entry(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + if (cse->matcher_stack_len == 0) { + raise_invalid_matcher_state(ctx); + } + + return &cse->matcher_stack[cse->matcher_stack_len-1]; +} + +static bool +matcher_current_val_in_mstack(apfl_ctx ctx, struct matcher_stack_entry *mstack, struct apfl_value *value) +{ + struct apfl_value cur; + + switch (mstack->mode) { + case MATCHER_MODE_VALUE: + case MATCHER_MODE_LIST_REMAINING: + if (!apfl_stack_get(ctx, &cur, -1)) { + raise_invalid_matcher_state(ctx); + } + *value = cur; + return true; + case MATCHER_MODE_STOP: + case MATCHER_MODE_LIST_UNDERFLOW: + return false; + case MATCHER_MODE_LIST_START: + if (!apfl_stack_get(ctx, &cur, -1)) { + raise_invalid_matcher_state(ctx); + } + if (cur.type != VALUE_LIST) { + raise_invalid_matcher_state(ctx); + } + if (mstack->lower >= cur.list->len) { + return false; + } + *value = cur.list->items[mstack->lower]; + return true; + case MATCHER_MODE_LIST_END: + if (!apfl_stack_get(ctx, &cur, -1)) { + raise_invalid_matcher_state(ctx); + } + if (cur.type != VALUE_LIST) { + raise_invalid_matcher_state(ctx); + } + if (mstack->upper == 0) { + return NULL; + } + *value = cur.list->items[mstack->upper-1]; + return true; + } + + raise_invalid_matcher_state(ctx); +} + +static bool +matcher_current_val(apfl_ctx ctx, struct matcher_call_stack_entry *cse, struct apfl_value *value) +{ + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + return matcher_current_val_in_mstack(ctx, mstack, value); +} + +static bool +matcher_next(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ +again:; + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + + switch (mstack->mode) { + case MATCHER_MODE_VALUE: + mstack->mode = MATCHER_MODE_STOP; + if (!apfl_stack_drop(ctx, -1)) { + raise_invalid_matcher_state(ctx); + } + return true; + case MATCHER_MODE_STOP: + case MATCHER_MODE_LIST_UNDERFLOW: + raise_invalid_matcher_state(ctx); + return false; + case MATCHER_MODE_LIST_START: + mstack->lower++; + return true; + case MATCHER_MODE_LIST_END: + if (mstack->upper <= mstack->lower) { + mstack->mode = MATCHER_MODE_LIST_UNDERFLOW; + return false; + } + mstack->upper--; + return true; + case MATCHER_MODE_LIST_REMAINING: + if (!apfl_stack_drop(ctx, -1)) { + raise_invalid_matcher_state(ctx); + } + matcher_stack_drop(ctx, cse); + goto again; // We also need to advance the previous stack entry, + // like we would do when doing a MATCHER_LEAVE_LIST + } + + raise_invalid_matcher_state(ctx); +} + +static bool +matcher_enter_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + struct apfl_value cur; + if (!matcher_current_val_in_mstack(ctx, mstack, &cur)) { + return false; + } + if (cur.type != VALUE_LIST) { + return false; + } + + size_t len = cur.list->len; + + apfl_stack_must_push(ctx, cur); + + matcher_stack_push(ctx, cse, (struct matcher_stack_entry) { + .mode = MATCHER_MODE_LIST_START, + .lower = 0, + .upper = len, + }); + return true; +} + +static void +matcher_continue_from_end(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + if (mstack->mode != MATCHER_MODE_LIST_START) { + raise_invalid_matcher_state(ctx); + } + mstack->mode = MATCHER_MODE_LIST_END; +} + +static void +matcher_remainding(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + struct apfl_value cur; + + if (!apfl_stack_get(ctx, &cur, -1)) { + raise_invalid_matcher_state(ctx); + } + + if ( + (mstack->mode != MATCHER_MODE_LIST_START && mstack->mode != MATCHER_MODE_LIST_END) + || cur.type != VALUE_LIST + ) { + raise_invalid_matcher_state(ctx); + } + + if (mstack->lower > mstack->upper) { + raise_invalid_matcher_state(ctx); + } + + struct list_header *cur_list = cur.list; + assert(cur_list->len >= mstack->upper); + + size_t len = mstack->upper - mstack->lower; + + apfl_list_create(ctx, len); + struct apfl_value new_val = apfl_stack_must_get(ctx, -1); + assert(new_val.type == VALUE_LIST); + + struct list_header *new_list = new_val.list; + assert(new_list->cap == len); + assert(new_list->len == 0); + for (size_t i = mstack->lower; i < mstack->upper; i++) { + new_list->items[new_list->len++] = cur_list->items[i]; + } + assert(new_list->len == len); + + if (!apfl_stack_drop(ctx, -2)) { // Drop the original list + raise_invalid_matcher_state(ctx); + } + + mstack->mode = MATCHER_MODE_LIST_REMAINING; +} + +static bool +matcher_leave_list(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + struct matcher_stack_entry *mstack = matcher_cur_stack_entry(ctx, cse); + if (mstack->mode != MATCHER_MODE_LIST_START) { + raise_invalid_matcher_state(ctx); + } + + if (mstack->lower < mstack->upper) { + // List was not completely matched + return false; + } + + if (!apfl_stack_drop(ctx, -1)) { + raise_invalid_matcher_state(ctx); + } + matcher_stack_drop(ctx, cse); + return matcher_next(ctx, cse); +} + +static void +return_from_matcher(apfl_ctx ctx, bool result) +{ + struct call_stack_entry *cse = apfl_call_stack_cur_entry(ctx); + assert(cse != NULL); + assert(cse->type == CSE_MATCHER); + + cse->matcher.matcher->result = result; + call_stack_drop(ctx); + + cse = apfl_call_stack_cur_entry(ctx); + assert(cse != NULL); + assert(cse->type == CSE_FUNCTION); +} + +#define RETURN_WITHOUT_MATCH(ctx) \ + do { \ + return_from_matcher((ctx), false); \ + return; \ + } while (0) + +#define RETURN_WITHOUT_MATCH_ON_FALSE(ctx, x) \ + do { \ + if (!(x)) { \ + RETURN_WITHOUT_MATCH(ctx); \ + } \ + } while (0) + +static void +evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) +{ + union matcher_instruction_or_arg arg; + + size_t *pc = &cse->pc; + struct matcher *matcher = cse->matcher; + struct matcher_instruction_list *milist = matcher->instructions; + + while (*pc < milist->len) { + 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)); + goto continue_loop; + case MATCHER_IGNORE: + if (!matcher_current_val(ctx, cse, &cur)) { + RETURN_WITHOUT_MATCH(ctx); + } + RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_next(ctx, cse)); + goto continue_loop; + case MATCHER_CHECK_CONST: + if (!matcher_current_val(ctx, cse, &cur)) { + RETURN_WITHOUT_MATCH(ctx); + } + must_get_matcher_argument(ctx, pc, milist, &arg); + matcher_check_index(ctx, milist->value_count, arg.index); + RETURN_WITHOUT_MATCH_ON_FALSE(ctx, apfl_value_eq(matcher->values[arg.index], cur)); + goto continue_loop; + case MATCHER_CHECK_PRED: + if (!matcher_current_val(ctx, cse, &cur)) { + RETURN_WITHOUT_MATCH(ctx); + } + must_get_matcher_argument(ctx, pc, milist, &arg); + matcher_check_index(ctx, milist->value_count, arg.index); + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.feature_not_implemented); // TODO: Implement this + goto continue_loop; + case MATCHER_ENTER_LIST: + RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_enter_list(ctx, cse)); + goto continue_loop; + case MATCHER_LEAVE_LIST: + RETURN_WITHOUT_MATCH_ON_FALSE(ctx, matcher_leave_list(ctx, cse)); + goto continue_loop; + case MATCHER_CONTINUE_FROM_END: + matcher_continue_from_end(ctx, cse); + goto continue_loop; + case MATCHER_REMAINDING: + matcher_remainding(ctx, cse); + goto continue_loop; + } + + assert(false); + continue_loop:; + } + + return_from_matcher( + ctx, + // We've successfully matched everything, if there's only one stack element left and we're in stop mode + cse->matcher_stack_len == 1 && cse->matcher_stack[0].mode == MATCHER_MODE_STOP + ); +} + struct apfl_iterative_runner_data { apfl_ctx ctx; apfl_tokenizer_ptr tokenizer; @@ -558,6 +1084,8 @@ iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr .scope = runner->scope, .closure_scope = NULL, .execution_line = ilist->line, + .returning_from_matcher = false, + .matcher = NULL, }, }); evaluate_until_call_stack_return(ctx); diff --git a/src/gc.c b/src/gc.c index 49a2a34..67c1b15 100644 --- a/src/gc.c +++ b/src/gc.c @@ -6,6 +6,7 @@ #include "bytecode.h" #include "context.h" #include "gc.h" +#include "matcher.h" #include "resizable.h" #include "scope.h" #include "value.h" @@ -29,6 +30,8 @@ struct gc_object { struct stack stack; struct function function; struct cfunction cfunction; + struct matcher_instruction_list matcher_instructions; + struct matcher matcher; }; enum gc_type type; enum gc_status status; @@ -172,6 +175,8 @@ IMPL_NEW(struct instruction_list, apfl_gc_new_instructions, GC_T IMPL_NEW(struct scope, apfl_gc_new_scope, GC_TYPE_SCOPE, scope ) IMPL_NEW(struct function, apfl_gc_new_func, GC_TYPE_FUNC, function ) IMPL_NEW(struct cfunction, apfl_gc_new_cfunc, GC_TYPE_CFUNC, cfunction ) +IMPL_NEW(struct matcher_instruction_list, apfl_gc_new_matcher_instructions, GC_TYPE_MATCHER_INSTRUCTIONS, matcher_instructions) +IMPL_NEW(struct matcher, apfl_gc_new_matcher, GC_TYPE_MATCHER, matcher ) size_t apfl_gc_tmproots_begin(struct gc *gc) @@ -271,6 +276,12 @@ visit_children(struct gc_object *object, gc_visitor cb, void *opaque) case GC_TYPE_CFUNC: apfl_gc_cfunc_traverse(&object->cfunction, cb, opaque); return; + case GC_TYPE_MATCHER_INSTRUCTIONS: + // Intentionally left blank. Object doesn't reference other objects. + return; + case GC_TYPE_MATCHER: + apfl_gc_matcher_traverse(&object->matcher, cb, opaque); + return; } assert(false); @@ -339,6 +350,12 @@ deinit_object(struct gc *gc, struct gc_object *object) case GC_TYPE_CFUNC: apfl_cfunction_deinit(gc->allocator, &object->cfunction); return; + case GC_TYPE_MATCHER_INSTRUCTIONS: + apfl_matcher_instructions_deinit(gc->allocator, &object->matcher_instructions); + return; + case GC_TYPE_MATCHER: + apfl_matcher_deinit(gc->allocator, &object->matcher); + return; } assert(false); @@ -427,6 +444,14 @@ apfl_gc_full(struct gc *gc) gc->is_collecting = false; } +void +apfl_gc_add_child(struct gc_object *parent, struct gc_object* child) +{ + if (parent->status == GC_STATUS_BLACK) { + color_object_grey(child); + } +} + static const char * dump_graph_bgcolor(enum gc_status status) { @@ -471,6 +496,10 @@ type_to_string(enum gc_type type) return "func"; case GC_TYPE_CFUNC: return "cfunc"; + case GC_TYPE_MATCHER_INSTRUCTIONS: + return "matcher instructions"; + case GC_TYPE_MATCHER: + return "matcher"; } assert(false); diff --git a/src/gc.h b/src/gc.h index 07bf016..168b868 100644 --- a/src/gc.h +++ b/src/gc.h @@ -29,6 +29,8 @@ enum gc_type { GC_TYPE_SCOPE, GC_TYPE_FUNC, GC_TYPE_CFUNC, + GC_TYPE_MATCHER_INSTRUCTIONS, + GC_TYPE_MATCHER, }; struct gc_tmproots { @@ -82,6 +84,8 @@ struct instruction_list* apfl_gc_new_instructions(struct gc *); struct scope* apfl_gc_new_scope(struct gc *); struct function* apfl_gc_new_func(struct gc *); struct cfunction* apfl_gc_new_cfunc(struct gc *); +struct matcher_instruction_list* apfl_gc_new_matcher_instructions(struct gc *); +struct matcher* apfl_gc_new_matcher(struct gc *); #ifdef __cplusplus } diff --git a/src/matcher.c b/src/matcher.c new file mode 100644 index 0000000..4e7ed3b --- /dev/null +++ b/src/matcher.c @@ -0,0 +1,79 @@ +#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, + .values = NULL, + .captures = NULL, + .result = false, + }; + + if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) { + goto error; + } + + if (!init_values_list(gc->allocator, &matcher.captures, milist->capture_count)) { + goto error; + } + + 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->instructions->value_count); + FREE_LIST(allocator, matcher->captures, matcher->instructions->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 new file mode 100644 index 0000000..fbe5cfa --- /dev/null +++ b/src/matcher.h @@ -0,0 +1,27 @@ +#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; + 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/messages.c b/src/messages.c index dc27a7e..0e57e41 100644 --- a/src/messages.c +++ b/src/messages.c @@ -18,4 +18,6 @@ const struct apfl_messages apfl_messages = { .not_a_function = "Not a function", .wrong_type = "Wrong type", .io_error = "I/O error", + .value_doesnt_match = "Value does not match", + .invalid_matcher_state = "Matcher is in invalid state", };