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 :)
This commit is contained in:
Laria 2022-01-14 23:16:19 +01:00
parent ae45aeebe2
commit a14b490dfe
4 changed files with 738 additions and 61 deletions

View file

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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)
{