From a14b490dfede4165e86d011a86dd700ed1ea5485 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Fri, 14 Jan 2022 23:16:19 +0100 Subject: [PATCH] Partially implement pattern matching assignments We're still missing predicates (need to be able to call functions for that one) and assignments into dictionaries. But we now can deconstruct a list and check against constants. So things like this work now: [1 foo ~bar [a b]] = [1 "Hello" 2 3 4 [5 6]] # foo is: "Hello" # bar is: [2 3 4] # a is: 5 # b is: 6 Pretty cool :) --- src/apfl.h | 3 + src/eval.c | 772 ++++++++++++++++++++++++++++++++++++++++++++++---- src/strings.c | 18 ++ src/value.c | 6 + 4 files changed, 738 insertions(+), 61 deletions(-) diff --git a/src/apfl.h b/src/apfl.h index 5c72e2e..4b9d3fe 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -81,6 +81,8 @@ struct apfl_string apfl_string_builder_move_string(struct apfl_string_builder *) #define apfl_string_builder_append_cstr(builder, cstr) (apfl_string_builder_append((builder), apfl_string_view_from_cstr((cstr)))) +apfl_refcounted_string apfl_string_copy_into_new_refcounted(struct apfl_string_view); + apfl_refcounted_string apfl_string_move_into_new_refcounted(struct apfl_string *); /* Increases the reference of the refcounted string. @@ -572,6 +574,7 @@ enum apfl_value_get_item_result { enum apfl_value_get_item_result apfl_value_get_item(struct apfl_value container, struct apfl_value key, struct apfl_value *out); apfl_list apfl_list_incref(apfl_list); +size_t apfl_list_len(apfl_list); bool apfl_list_get_item(apfl_list, size_t index, struct apfl_value *out); void apfl_list_unref(apfl_list); diff --git a/src/eval.c b/src/eval.c index 04afe5b..1198ef6 100644 --- a/src/eval.c +++ b/src/eval.c @@ -16,10 +16,62 @@ struct variable_data { typedef struct variable_data *variable; +enum match_result { + MATCH_OK, + MATCH_DOESNT_MATCH, + MATCH_ERROR, + MATCH_FATAL_ERROR, + MATCH_NOT_YET_IMPLEMENTED, +}; + +enum match_pattern_type { + MPATTERN_BLANK, + MPATTERN_VALUE, + MPATTERN_LIST, +}; + +struct match_pattern_value { + apfl_refcounted_string varname; + variable var; + struct apfl_value value; + struct apfl_value *member_keys; + size_t member_keys_len; +}; + +struct match_pattern_list { + struct match_pattern *subpatterns; + size_t subpatterns_len; + bool with_expand; + size_t expand_index; +}; + +enum match_pattern_constraint_type { + MPATTERN_CONSTRAINT_EQUALS, + MPATTERN_CONSTRAINT_PREDICATE, +}; + +struct match_pattern_constraint { + enum match_pattern_constraint_type type; + struct apfl_value value; +}; + +struct match_pattern { + enum match_pattern_type type; + union { + struct match_pattern_value value; + struct match_pattern_list list; + }; + struct match_pattern_constraint *constraints; + size_t constraints_len; +}; + static struct apfl_result evaluate(apfl_ctx, struct apfl_expr *); static variable variable_new(void); static variable variable_incref(variable var); static void variable_unref(variable var); +static enum match_result match_pattern_from_assignable(apfl_ctx ctx, struct apfl_expr_assignable *, struct match_pattern *); +static void match_pattern_deinit(struct match_pattern *); +static enum match_result match_pattern_match(struct match_pattern *, struct apfl_value); static bool scope_keys_eq(void *opaque, const void *_a, const void *_b) @@ -127,6 +179,17 @@ variable_unref(variable var) } } +static void +variable_set(variable var, struct apfl_value value) +{ + if (var == NULL) { + return; + } + + apfl_value_deinit(&var->value); + var->value = apfl_value_move(&value); +} + apfl_ctx apfl_ctx_new(void) { @@ -191,6 +254,576 @@ ctx_get_var(apfl_ctx ctx, apfl_refcounted_string name) return ok ? var : NULL; } +static bool +constant_to_value(struct apfl_expr_const *constant, struct apfl_value *value) +{ + apfl_refcounted_string rcstring; + + switch (constant->type) { + case APFL_EXPR_CONST_NIL: + value->type = APFL_VALUE_NIL; + return true; + case APFL_EXPR_CONST_BOOLEAN: + value->type = APFL_VALUE_BOOLEAN; + value->boolean = constant->boolean; + return true; + case APFL_EXPR_CONST_STRING: + // TODO: Moving the string will become a problem when we're evaluating the same AST node twice. + // The parser probably should already return rcstrings. + rcstring = apfl_string_move_into_new_refcounted(&constant->string); + if (rcstring == NULL) { + return false; + } + value->type = APFL_VALUE_STRING; + value->string = rcstring; + return true; + case APFL_EXPR_CONST_NUMBER: + value->type = APFL_VALUE_NUMBER; + value->number = constant->number; + return true; + } + + assert(false); + return false; +} + +static bool +match_pattern_add_constraint( + struct match_pattern *pattern, + size_t *constraints_cap, + enum match_pattern_constraint_type type, + struct apfl_value value +) { + struct match_pattern_constraint constraint = { + .type = type, + .value = value, + }; + + if (!apfl_resizable_append( + sizeof(struct match_pattern_constraint), + (void **)&pattern->constraints, + &pattern->constraints_len, + constraints_cap, + &constraint, + 1 + )) { + apfl_value_deinit(&value); + return false; + } + return true; +} + +static enum match_result +match_pattern_from_assignable_list( + apfl_ctx ctx, + struct apfl_expr_assignable_list *assignable_list, + struct match_pattern *pattern +) { + pattern->type = MPATTERN_LIST; + struct match_pattern_list *pattern_list = &pattern->list; + *pattern_list = (struct match_pattern_list) { + .subpatterns = NULL, + .subpatterns_len = 0, + .with_expand = false, + .expand_index = 0, + }; + + if (assignable_list->len == 0) { + return MATCH_OK; + } + + if ((pattern_list->subpatterns = ALLOC_LIST( + struct match_pattern, + assignable_list->len + )) == NULL) { + return MATCH_FATAL_ERROR; + } + + for (size_t i = 0; i < assignable_list->len; i++) { + struct apfl_expr_assignable_list_item *item = &assignable_list->items[i]; + + if (item->expand) { + if (pattern_list->with_expand) { + return MATCH_ERROR; + } + + pattern_list->with_expand = true; + pattern_list->expand_index = i; + } + + enum match_result result = match_pattern_from_assignable( + ctx, + &item->assignable, + &pattern_list->subpatterns[i] + ); + + if (result != MATCH_OK) { + DEINIT_LIST( + pattern_list->subpatterns, + pattern_list->subpatterns_len, + match_pattern_deinit + ); + return result; + } + + pattern_list->subpatterns_len++; + } + + return MATCH_OK; +} + +static enum match_result +match_pattern_from_var_or_member( + apfl_ctx ctx, + struct apfl_expr_assignable_var_or_member *var_or_member, + struct match_pattern *pattern +) { + pattern->type = MPATTERN_VALUE; + pattern->value = (struct match_pattern_value) { + .varname = NULL, + .var = NULL, + .value = {.type = APFL_VALUE_NIL}, + .member_keys = NULL, + .member_keys_len = 0, + }; + size_t member_keys_cap = 0; + + enum match_result result; + struct apfl_result eval_result; + struct apfl_value str_value; + +next: + switch (var_or_member->type) { + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR: + if ((pattern->value.varname = apfl_string_copy_into_new_refcounted( + apfl_string_view_from(var_or_member->var) + )) == NULL) { + result = MATCH_FATAL_ERROR; + goto fail; + } + return MATCH_OK; + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT: + str_value.type = APFL_VALUE_STRING; + if ((str_value.string = apfl_string_copy_into_new_refcounted( + apfl_string_view_from(var_or_member->dot.rhs) + )) == NULL) { + result = MATCH_FATAL_ERROR; + goto fail; + } + + if (!apfl_resizable_append( + sizeof(struct apfl_value), + (void **)&pattern->value.member_keys, + &pattern->value.member_keys_len, + &member_keys_cap, + &str_value, + 1 + )) { + apfl_value_deinit(&str_value); + result = MATCH_FATAL_ERROR; + goto fail; + } + + var_or_member = var_or_member->dot.lhs; + goto next; + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT: + eval_result = evaluate(ctx, var_or_member->at.rhs); + switch (eval_result.type) { + case APFL_RESULT_OK: + break; + case APFL_RESULT_ERR: + result = MATCH_ERROR; + goto fail; + case APFL_RESULT_ERR_FATAL: + result = MATCH_FATAL_ERROR; + goto fail; + } + + if (!apfl_resizable_append( + sizeof(struct apfl_value), + (void **)&pattern->value.member_keys, + &pattern->value.member_keys_len, + &member_keys_cap, + &eval_result.value, + 1 + )) { + apfl_value_deinit(&eval_result.value); + result = MATCH_FATAL_ERROR; + goto fail; + } + + var_or_member = var_or_member->at.lhs; + goto next; + } + +fail: + DEINIT_LIST(pattern->value.member_keys, pattern->value.member_keys_len, apfl_value_deinit); + return result; +} + + +static enum match_result +match_pattern_from_assignable_inner( + apfl_ctx ctx, + struct apfl_expr_assignable *assignable, + struct match_pattern *pattern +) { + struct apfl_value value; + struct apfl_result result; + + pattern->type = MPATTERN_BLANK; + pattern->constraints = NULL; + pattern->constraints_len = 0; + + size_t constraints_cap = 0; + +next: + switch (assignable->type) { + case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER: + return match_pattern_from_var_or_member(ctx, &assignable->var_or_member, pattern); + case APFL_EXPR_ASSIGNABLE_CONSTANT: + if (!constant_to_value(&assignable->constant, &value)) { + return MATCH_FATAL_ERROR; + } + if (!match_pattern_add_constraint( + pattern, + &constraints_cap, + MPATTERN_CONSTRAINT_EQUALS, + value + )) { + return MATCH_FATAL_ERROR; + } + pattern->type = MPATTERN_BLANK; + return MATCH_OK; + case APFL_EXPR_ASSIGNABLE_PREDICATE: + result = evaluate(ctx, assignable->predicate.rhs); + switch (result.type) { + case APFL_RESULT_OK: + break; + case APFL_RESULT_ERR: + return MATCH_ERROR; + case APFL_RESULT_ERR_FATAL: + return MATCH_FATAL_ERROR; + } + + if (!match_pattern_add_constraint( + pattern, + &constraints_cap, + MPATTERN_CONSTRAINT_PREDICATE, + apfl_value_move(&result.value) + )) { + return MATCH_FATAL_ERROR; + } + + assignable = assignable->predicate.lhs; + goto next; + case APFL_EXPR_ASSIGNABLE_LIST: + return match_pattern_from_assignable_list(ctx, &assignable->list, pattern); + case APFL_EXPR_ASSIGNABLE_BLANK: + pattern->type = MPATTERN_BLANK; + return MATCH_OK; + } + + assert(false); + return MATCH_FATAL_ERROR; +} + +static enum match_result +match_pattern_from_assignable( + apfl_ctx ctx, + struct apfl_expr_assignable *assignable, + struct match_pattern *pattern +) { + enum match_result result = match_pattern_from_assignable_inner(ctx, assignable, pattern); + if (result != MATCH_OK) { + match_pattern_deinit(pattern); + } + return result; +} + +static bool +match_pattern_create_vars(apfl_ctx ctx, struct match_pattern *pattern) +{ + switch (pattern->type) { + case MPATTERN_BLANK: + return true; + case MPATTERN_VALUE: + if (pattern->value.var != NULL) { + return true; + } + + if ((pattern->value.var = ctx_get_var_for_assignment( + ctx, + apfl_refcounted_string_incref(pattern->value.varname) + )) == NULL) { + return false; + } + + return true; + case MPATTERN_LIST: + for (size_t i = 0; i < pattern->list.subpatterns_len; i++) { + if (!match_pattern_create_vars(ctx, &pattern->list.subpatterns[i])) { + return false; + } + } + return true; + } + + assert(false); + return false; +} + +static void +match_pattern_constraint_deinit(struct match_pattern_constraint *constraint) +{ + if (constraint == NULL) { + return; + } + + apfl_value_deinit(&constraint->value); +} + +static void +match_pattern_deinit(struct match_pattern *pattern) +{ + if (pattern == NULL) { + return; + } + + switch (pattern->type) { + case MPATTERN_BLANK: + break; + case MPATTERN_VALUE: + apfl_refcounted_string_unref(pattern->value.varname); + variable_unref(pattern->value.var); + apfl_value_deinit(&pattern->value.value); + DEINIT_LIST( + pattern->value.member_keys, + pattern->value.member_keys_len, + apfl_value_deinit + ); + break; + case MPATTERN_LIST: + DEINIT_LIST( + pattern->list.subpatterns, + pattern->list.subpatterns_len, + match_pattern_deinit + ); + break; + } + + DEINIT_LIST( + pattern->constraints, + pattern->constraints_len, + match_pattern_constraint_deinit + ); +} + +static enum match_result +match_pattern_check_constraint( + struct match_pattern_constraint *constraint, + const struct apfl_value *value +) { + switch (constraint->type) { + case MPATTERN_CONSTRAINT_PREDICATE: + return MATCH_NOT_YET_IMPLEMENTED; + case MPATTERN_CONSTRAINT_EQUALS: + if (apfl_value_eq(constraint->value, *value)) { + return MATCH_OK; + } + return MATCH_DOESNT_MATCH; + } + + assert(false); + return MATCH_FATAL_ERROR; +} + +static enum match_result +match_pattern_match_list_inner( + struct match_pattern_list *pattern_list, + /*borrowed*/ apfl_list list +) { + size_t list_len = apfl_list_len(list); + + if (pattern_list->with_expand + ? (list_len < pattern_list->subpatterns_len - 1) + : (pattern_list->subpatterns_len != list_len) + ) { + return MATCH_DOESNT_MATCH; + } + + size_t limit = pattern_list->with_expand + ? pattern_list->expand_index + : pattern_list->subpatterns_len; + + for (size_t i = 0; i < limit; i++) { + struct apfl_value list_item; + // Will not fail, as i is a valid index for the list + assert(apfl_list_get_item( + apfl_list_incref(list), + i, + &list_item + )); + + enum match_result result = match_pattern_match( + &pattern_list->subpatterns[i], + apfl_value_move(&list_item) + ); + if (result != MATCH_OK) { + return result; + } + } + + if (!pattern_list->with_expand) { + return MATCH_OK; + } + + size_t tail_len = pattern_list->subpatterns_len - pattern_list->expand_index - 1; + + for (size_t i = 0; i < tail_len; i++) { + size_t subpattern_index = pattern_list->subpatterns_len - i - 1; + size_t list_index = list_len - i - 1; + + struct apfl_value list_item; + // Will not fail, as list_index is a valid index for the list + assert(apfl_list_get_item( + apfl_list_incref(list), + list_index, + &list_item + )); + + enum match_result result = match_pattern_match( + &pattern_list->subpatterns[subpattern_index], + apfl_value_move(&list_item) + ); + if (result != MATCH_OK) { + return result; + } + } + + apfl_editable_list mid_builder = apfl_editable_list_new(); + if (mid_builder == NULL) { + return MATCH_ERROR; + } + + for (size_t i = pattern_list->expand_index; i < list_len - tail_len; i++) { + struct apfl_value list_item; + // Will not fail, as i is a valid index for the list + assert(apfl_list_get_item( + apfl_list_incref(list), + i, + &list_item + )); + if (!apfl_editable_list_append( + mid_builder, + apfl_value_move(&list_item)) + ) { + apfl_editable_list_destroy(mid_builder); + return MATCH_FATAL_ERROR; + } + } + + apfl_list mid_list = apfl_editable_list_finalize(mid_builder); + if (mid_list == NULL) { + return MATCH_FATAL_ERROR; + } + + return match_pattern_match( + &pattern_list->subpatterns[pattern_list->expand_index], + (struct apfl_value) { + .type = APFL_VALUE_LIST, + .list = mid_list, + } + ); +} + +static enum match_result +match_pattern_match_list( + struct match_pattern_list *pattern_list, + struct apfl_value value +) { + if (value.type != APFL_VALUE_LIST) { + apfl_value_deinit(&value); + return MATCH_DOESNT_MATCH; + } + apfl_list list = apfl_list_incref(value.list); + apfl_value_deinit(&value); + + enum match_result result = match_pattern_match_list_inner(pattern_list, list); + apfl_list_unref(list); + return result; +} + +static enum match_result +match_pattern_match(struct match_pattern *pattern, struct apfl_value value) +{ + // We put the contraints into the list from the outside in, so we need + // to iterate in reverse order. + for (size_t i = pattern->constraints_len; i-- > 0; ) { + enum match_result result = match_pattern_check_constraint( + &pattern->constraints[i], + &value + ); + if (result != MATCH_OK) { + apfl_value_deinit(&value); + return result; + } + } + + switch (pattern->type) { + case MPATTERN_BLANK: + apfl_value_deinit(&value); + return MATCH_OK; + case MPATTERN_VALUE: + pattern->value.value = apfl_value_move(&value); + return MATCH_OK; + case MPATTERN_LIST: + return match_pattern_match_list(&pattern->list, apfl_value_move(&value)); + } + + assert(false); + return MATCH_FATAL_ERROR; +} + +static enum match_result +match_pattern_apply_value(struct match_pattern_value *pattern_value) +{ + if (pattern_value->var == NULL) { + return false; + } + + if (pattern_value->member_keys_len > 0) { + return MATCH_NOT_YET_IMPLEMENTED; + } + + variable_set(pattern_value->var, apfl_value_move(&pattern_value->value)); + return MATCH_OK; +} + +static enum match_result +match_pattern_apply(struct match_pattern *pattern) +{ + switch (pattern->type) { + case MPATTERN_BLANK: + return MATCH_OK; + case MPATTERN_VALUE: + return match_pattern_apply_value(&pattern->value); + case MPATTERN_LIST: + for (size_t i = 0; i < pattern->list.subpatterns_len; i++) { + enum match_result result = match_pattern_apply( + &pattern->list.subpatterns[i] + ); + if (result != MATCH_OK) { + return result; + } + } + return MATCH_OK; + } + + assert(false); + return MATCH_FATAL_ERROR; +} + static struct apfl_result fatal(void) { @@ -200,48 +833,15 @@ fatal(void) static struct apfl_result evaluate_constant(struct apfl_expr_const *constant) { - apfl_refcounted_string rcstring; - - switch (constant->type) { - case APFL_EXPR_CONST_NIL: - return (struct apfl_result) { - .type = APFL_RESULT_OK, - .value = { - .type = APFL_VALUE_NIL, - }, - }; - case APFL_EXPR_CONST_BOOLEAN: - return (struct apfl_result) { - .type = APFL_RESULT_OK, - .value = { - .type = APFL_VALUE_BOOLEAN, - .boolean = constant->boolean, - }, - }; - case APFL_EXPR_CONST_STRING: - rcstring = apfl_string_move_into_new_refcounted(&constant->string); - if (rcstring == NULL) { - return fatal(); - } - - return (struct apfl_result) { - .type = APFL_RESULT_OK, - .value = { - .type = APFL_VALUE_STRING, - .string = rcstring, - }, - }; - case APFL_EXPR_CONST_NUMBER: - return (struct apfl_result) { - .type = APFL_RESULT_OK, - .value = { - .type = APFL_VALUE_NUMBER, - .number = constant->number, - }, - }; + struct apfl_value value; + if (!constant_to_value(constant, &value)) { + return fatal(); } - assert(false); + return (struct apfl_result) { + .type = APFL_RESULT_OK, + .value = value, + }; } static struct apfl_result @@ -433,38 +1033,88 @@ evaluate_at(apfl_ctx ctx, struct apfl_expr_at *at) } } +static struct apfl_result +failing_match_result_to_apfl_result(enum match_result match_result) +{ + if (match_result == MATCH_FATAL_ERROR) { + return fatal(); + } + return (struct apfl_result) { .type = APFL_RESULT_ERR }; +} + +static enum match_result +evaluate_assignment_setup( + apfl_ctx ctx, + struct match_pattern *pattern, + struct apfl_expr_assignment *assignment +) { + enum match_result result = match_pattern_from_assignable( + ctx, + &assignment->lhs, + pattern + ); + + if (result != MATCH_OK) { + return result; + } + + if (!match_pattern_create_vars(ctx, pattern)) { + match_pattern_deinit(pattern); + return MATCH_FATAL_ERROR; + } + + return MATCH_OK; +} + +static enum match_result +evaluate_assignment_finish( + struct match_pattern *pattern, + struct apfl_value value +) { + enum match_result match_result = match_pattern_match( + pattern, + apfl_value_move(&value) + ); + + if (match_result != MATCH_OK) { + return match_result; + } + + return match_pattern_apply(pattern); +} + + static struct apfl_result evaluate_assignment(apfl_ctx ctx, struct apfl_expr_assignment *assignment) { - // TODO: Use assignment->local flag. Sonce we don't hev functions yet, it's not yet relevant. + struct match_pattern pattern; + enum match_result match_result = evaluate_assignment_setup( + ctx, + &pattern, + assignment + ); - if ( - assignment->lhs.type != APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER - || assignment->lhs.var_or_member.type != APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR - ) { - return (struct apfl_result) { .type = APFL_RESULT_ERR }; // TODO: Implement other assignable types - } - - apfl_refcounted_string varname = apfl_string_move_into_new_refcounted(&assignment->lhs.var_or_member.var); - if (varname == NULL) { - return fatal(); - } - - variable var = ctx_get_var_for_assignment(ctx, varname); - - if (var == NULL) { - return fatal(); + if (match_result != MATCH_OK) { + return failing_match_result_to_apfl_result(match_result); } struct apfl_result result = evaluate(ctx, assignment->rhs); if (result.type != APFL_RESULT_OK) { - variable_unref(var); + match_pattern_deinit(&pattern); return result; } - apfl_value_deinit(&var->value); - var->value = apfl_value_incref(result.value); - variable_unref(var); + match_result = evaluate_assignment_finish( + &pattern, + apfl_value_incref(result.value) + ); + + match_pattern_deinit(&pattern); + if (match_result != MATCH_OK) { + apfl_value_deinit(&result.value); + return failing_match_result_to_apfl_result(match_result); + } + return result; } diff --git a/src/strings.c b/src/strings.c index ae7bb80..2e524af 100644 --- a/src/strings.c +++ b/src/strings.c @@ -157,11 +157,29 @@ apfl_string_view_from_refcounted_string(apfl_refcounted_string rcstring) return apfl_string_view_from(rcstring->string); } +apfl_refcounted_string +apfl_string_copy_into_new_refcounted(struct apfl_string_view sv) +{ + struct apfl_string str = apfl_string_blank(); + if (!apfl_string_copy(&str, sv)) { + return NULL; + } + + apfl_refcounted_string rcstring = apfl_string_move_into_new_refcounted(&str); + if (rcstring == NULL) { + apfl_string_deinit(&str); + return NULL; + } + + return rcstring; +} + apfl_refcounted_string apfl_string_move_into_new_refcounted(struct apfl_string *src) { apfl_refcounted_string dst = ALLOC(struct apfl_refcounted_string_data); if (dst == NULL) { + // TODO: Or should we free src here? return NULL; } diff --git a/src/value.c b/src/value.c index 9f6704e..8d19ae9 100644 --- a/src/value.c +++ b/src/value.c @@ -28,6 +28,12 @@ apfl_list_incref(apfl_list list) return list; } +size_t +apfl_list_len(apfl_list list) +{ + return list->len; +} + void apfl_list_unref(apfl_list list) {