#include #include "apfl.h" #include "alloc.h" #include "hashmap.h" #include "internal.h" #include "resizable.h" #include "value.h" #define TRY(ex) \ do { \ enum apfl_result result = (ex); \ if (result != APFL_RESULT_OK) { \ return result; \ } \ } while (0) struct apfl_ctx_data { struct apfl_allocator allocator; struct apfl_hashmap scope; struct apfl_value *stack; size_t stack_len; size_t stack_cap; }; struct variable_data { unsigned refcount; struct apfl_value value; }; 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; size_t member_keys_cap; }; 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; size_t constraints_cap; }; static enum apfl_result evaluate(apfl_ctx, struct apfl_expr *); static variable variable_new(apfl_ctx); static variable variable_incref(variable var); static void variable_unref(apfl_ctx, 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(apfl_ctx, struct match_pattern *); static enum match_result match_pattern_match(struct apfl_allocator, struct match_pattern *, struct apfl_value value); static bool scope_keys_eq(void *opaque, const void *_a, const void *_b) { (void)opaque; apfl_refcounted_string const *a = _a; apfl_refcounted_string const *b = _b; return *a == *b || apfl_string_eq(*a, *b); } static apfl_hash scope_calc_hash(void *opaque, const void *_key) { (void)opaque; apfl_refcounted_string const *key = _key; struct apfl_string_view sv = apfl_string_view_from(*key); return apfl_hash_fnv1a(sv.bytes, sv.len); } static void scope_destroy_key(void *opaque, void *_key) { apfl_ctx ctx = opaque; apfl_refcounted_string *key = _key; apfl_refcounted_string_unref(ctx->allocator, *key); } static void scope_destroy_value(void *opaque, void *_value) { apfl_ctx ctx = opaque; variable *value = _value; variable_unref(ctx, *value); } static void scope_copy_key(void *opaque, void *_dest, void *_src) { (void)opaque; apfl_refcounted_string *dest = _dest; apfl_refcounted_string *src = _src; *dest = apfl_refcounted_string_incref(*src); } static void scope_copy_value(void *opaque, void *_dest, void *_src) { (void)opaque; variable *dest = _dest; variable *src = _src; *dest = variable_incref(*src); } bool scope_init(apfl_ctx ctx, struct apfl_hashmap *map) { return apfl_hashmap_init( map, ctx->allocator, (struct apfl_hashmap_callbacks) { .opaque = ctx, .keys_eq = scope_keys_eq, .calc_hash = scope_calc_hash, .destroy_key = scope_destroy_key, .destroy_value = scope_destroy_value, .copy_key = scope_copy_key, .copy_value = scope_copy_value, }, sizeof(apfl_refcounted_string), sizeof(variable) ); } static variable variable_new(apfl_ctx ctx) { variable var = ALLOC_OBJ(ctx->allocator, struct variable_data); if (var == NULL) { return NULL; } var->refcount = 1; var->value.type = APFL_VALUE_NIL; return var; } static variable variable_incref(variable var) { if (var != NULL) { var->refcount++; } return var; } static void variable_unref(apfl_ctx ctx, variable var) { if (var != NULL && apfl_refcount_dec(&var->refcount)) { apfl_value_deinit(ctx->allocator, &var->value); FREE_OBJ(ctx->allocator, var); } } static void variable_set(apfl_ctx ctx, variable var, struct apfl_value value) { if (var == NULL) { return; } apfl_value_deinit(ctx->allocator, &var->value); var->value = apfl_value_move(&value); } static bool stack_push(apfl_ctx ctx, struct apfl_value value) { bool ok = apfl_resizable_append( ctx->allocator, sizeof(struct apfl_value), (void **)&ctx->stack, &ctx->stack_len, &ctx->stack_cap, &value, 1 ); if (!ok) { apfl_value_deinit(ctx->allocator, &value); } return ok; } static bool stack_check_index(apfl_ctx ctx, int *index) { if (*index < 0) { if ((size_t)-*index > ctx->stack_len) { return false; } *index = ctx->stack_len + *index; } else if ((size_t)*index >= ctx->stack_len) { return false; } assert(0 <= *index && (size_t)*index < ctx->stack_len); return true; } static bool stack_pop(apfl_ctx ctx, struct apfl_value *value, int index) { if (!stack_check_index(ctx, &index)) { return false; } *value = ctx->stack[index]; assert(apfl_resizable_splice( ctx->allocator, sizeof(struct apfl_value), (void **)ctx->stack, &ctx->stack_len, &ctx->stack_cap, index, 1, NULL, 0 )); return true; } static struct apfl_value stack_must_pop(apfl_ctx ctx, int index) { struct apfl_value value; assert(stack_pop(ctx, &value, index)); return value; } static bool stack_get(apfl_ctx ctx, struct apfl_value *value, int index) { if (!stack_check_index(ctx, &index)) { return false; } *value = apfl_value_incref(ctx->stack[index]); return true; } static struct apfl_value stack_must_get(apfl_ctx ctx, int index) { struct apfl_value value; assert(stack_get(ctx, &value, index)); return value; } static bool stack_drop(apfl_ctx ctx, int index) { struct apfl_value value; if (!stack_pop(ctx, &value, index)) { return false; } apfl_value_deinit(ctx->allocator, &value); return true; } static void stack_must_drop(apfl_ctx ctx, int index) { assert(stack_drop(ctx, index)); } apfl_ctx apfl_ctx_new(struct apfl_allocator allocator) { apfl_ctx ctx = ALLOC_OBJ(allocator, struct apfl_ctx_data); if (ctx == NULL) { return NULL; } ctx->allocator = allocator; ctx->stack = NULL; ctx->stack_len = 0; ctx->stack_cap = 0; if (!scope_init(ctx, &ctx->scope)) { FREE_OBJ(allocator, ctx); return NULL; } return ctx; } void apfl_ctx_destroy(apfl_ctx ctx) { if (ctx == NULL) { return; } apfl_hashmap_deinit(&ctx->scope); while (ctx->stack_len > 0) { stack_must_drop(ctx, -1); } FREE_LIST(ctx->allocator, ctx->stack, ctx->stack_cap); FREE_OBJ(ctx->allocator, ctx); } static variable ctx_get_var_for_assignment_inner(apfl_ctx ctx, /*borrowed*/ apfl_refcounted_string name) { variable var; if (apfl_hashmap_get(&ctx->scope, &name, &var)) { return var; } if ((var = variable_new(ctx)) == NULL) { return NULL; } if (!apfl_hashmap_set(&ctx->scope, &name, &var)) { variable_unref(ctx, var); return NULL; } return var; } static variable ctx_get_var_for_assignment(apfl_ctx ctx, apfl_refcounted_string name) { variable var = ctx_get_var_for_assignment_inner(ctx, name); apfl_refcounted_string_unref(ctx->allocator, name); return var; } static variable ctx_get_var(apfl_ctx ctx, apfl_refcounted_string name) { variable var; bool ok = apfl_hashmap_get(&ctx->scope, &name, &var); apfl_refcounted_string_unref(ctx->allocator, name); return ok ? var : NULL; } static struct apfl_value constant_to_value(struct apfl_expr_const *constant) { switch (constant->type) { case APFL_EXPR_CONST_NIL: return (struct apfl_value) { .type = APFL_VALUE_NIL }; case APFL_EXPR_CONST_BOOLEAN: return (struct apfl_value) { .type = APFL_VALUE_BOOLEAN, .boolean = constant->boolean, }; case APFL_EXPR_CONST_STRING: return (struct apfl_value) { .type = APFL_VALUE_STRING, .string = apfl_refcounted_string_incref(constant->string), }; case APFL_EXPR_CONST_NUMBER: return (struct apfl_value) { .type = APFL_VALUE_NUMBER, .number = constant->number, }; } assert(false); return (struct apfl_value) { .type = APFL_VALUE_NIL }; } static bool match_pattern_add_constraint( struct apfl_allocator allocator, struct match_pattern *pattern, enum match_pattern_constraint_type type, struct apfl_value value ) { struct match_pattern_constraint constraint = { .type = type, .value = value, }; if (!apfl_resizable_append( allocator, sizeof(struct match_pattern_constraint), (void **)&pattern->constraints, &pattern->constraints_len, &pattern->constraints_cap, &constraint, 1 )) { apfl_value_deinit(allocator, &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( ctx->allocator, 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_CAP_LIST_WITH_ARGS( ctx->allocator, pattern_list->subpatterns, pattern_list->subpatterns_len, assignable_list->len, match_pattern_deinit, ctx ); 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, .member_keys_cap = 0, }; enum match_result result; enum apfl_result eval_result; struct apfl_value str_value; struct apfl_value rhs_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( ctx->allocator, 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( ctx->allocator, apfl_string_view_from(var_or_member->dot.rhs) )) == NULL) { result = MATCH_FATAL_ERROR; goto fail; } if (!apfl_resizable_append( ctx->allocator, sizeof(struct apfl_value), (void **)&pattern->value.member_keys, &pattern->value.member_keys_len, &pattern->value.member_keys_cap, &str_value, 1 )) { apfl_value_deinit(ctx->allocator, &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) { 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; } rhs_value = stack_must_pop(ctx, -1); if (!apfl_resizable_append( ctx->allocator, sizeof(struct apfl_value), (void **)&pattern->value.member_keys, &pattern->value.member_keys_len, &pattern->value.member_keys_cap, &rhs_value, 1 )) { apfl_value_deinit(ctx->allocator, &rhs_value); result = MATCH_FATAL_ERROR; goto fail; } var_or_member = var_or_member->at.lhs; goto next; } fail: DEINIT_LIST( ctx->allocator, pattern->value.member_keys, pattern->value.member_keys_cap, 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; pattern->type = MPATTERN_BLANK; pattern->constraints = NULL; pattern->constraints_len = 0; pattern->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 (!match_pattern_add_constraint( ctx->allocator, pattern, MPATTERN_CONSTRAINT_EQUALS, constant_to_value(&assignable->constant) )) { return MATCH_FATAL_ERROR; } pattern->type = MPATTERN_BLANK; return MATCH_OK; case APFL_EXPR_ASSIGNABLE_PREDICATE: switch (evaluate(ctx, assignable->predicate.rhs)) { case APFL_RESULT_OK: break; case APFL_RESULT_ERR: return MATCH_ERROR; case APFL_RESULT_ERR_FATAL: return MATCH_FATAL_ERROR; } if (!stack_pop(ctx, &value, -1)) { return MATCH_FATAL_ERROR; } if (!match_pattern_add_constraint( ctx->allocator, pattern, MPATTERN_CONSTRAINT_PREDICATE, apfl_value_move(&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(ctx, 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 apfl_allocator allocator, struct match_pattern_constraint *constraint) { if (constraint == NULL) { return; } apfl_value_deinit(allocator, &constraint->value); } static void match_pattern_deinit(apfl_ctx ctx, struct match_pattern *pattern) { if (pattern == NULL) { return; } switch (pattern->type) { case MPATTERN_BLANK: break; case MPATTERN_VALUE: apfl_refcounted_string_unref(ctx->allocator, pattern->value.varname); variable_unref(ctx, pattern->value.var); apfl_value_deinit(ctx->allocator, &pattern->value.value); DEINIT_LIST( ctx->allocator, pattern->value.member_keys, pattern->value.member_keys_cap, apfl_value_deinit ); break; case MPATTERN_LIST: DEINIT_LIST_WITH_ARGS( ctx->allocator, pattern->list.subpatterns, pattern->list.subpatterns_len, match_pattern_deinit, ctx ); break; } DEINIT_LIST( ctx->allocator, pattern->constraints, pattern->constraints_cap, 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 apfl_allocator allocator, 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( allocator, apfl_list_incref(list), i, &list_item )); enum match_result result = match_pattern_match( allocator, &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( allocator, apfl_list_incref(list), list_index, &list_item )); enum match_result result = match_pattern_match( allocator, &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(allocator); 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( allocator, 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( allocator, &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 apfl_allocator allocator, struct match_pattern_list *pattern_list, struct apfl_value value ) { if (value.type != APFL_VALUE_LIST) { apfl_value_deinit(allocator, &value); return MATCH_DOESNT_MATCH; } apfl_list list = apfl_list_incref(value.list); apfl_value_deinit(allocator, &value); enum match_result result = match_pattern_match_list_inner(allocator, pattern_list, list); apfl_list_unref(allocator, list); return result; } static enum match_result match_pattern_match(struct apfl_allocator allocator, 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(allocator, &value); return result; } } switch (pattern->type) { case MPATTERN_BLANK: apfl_value_deinit(allocator, &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(allocator, &pattern->list, apfl_value_move(&value)); } assert(false); return MATCH_FATAL_ERROR; } static enum match_result match_pattern_apply_value_keys( struct apfl_allocator allocator, struct match_pattern_value *pattern_value, apfl_editable_dict ed, size_t i ) { struct apfl_value *key = &pattern_value->member_keys[i]; if (i == 0) { if (!apfl_editable_dict_set( ed, apfl_value_incref(*key), apfl_value_move(&pattern_value->value) )) { return MATCH_FATAL_ERROR; } return MATCH_OK; } apfl_editable_dict next_ed; struct apfl_value value; if (apfl_editable_dict_get( ed, apfl_value_incref(*key), &value )) { if (value.type != APFL_VALUE_DICT) { apfl_value_deinit(allocator, &value); return MATCH_ERROR; } next_ed = apfl_editable_dict_new_from_dict(allocator, value.dict); } else { // Be nice and create the missing dictionary next_ed = apfl_editable_dict_new(allocator); } if (next_ed == NULL) { return MATCH_FATAL_ERROR; } enum match_result result = match_pattern_apply_value_keys( allocator, pattern_value, next_ed, i - 1 ); if (result != MATCH_OK) { apfl_editable_dict_destroy(next_ed); return result; } apfl_dict next_dict = apfl_editable_dict_finalize(next_ed); if (next_dict == NULL) { return MATCH_FATAL_ERROR; } if (!apfl_editable_dict_set( ed, apfl_value_incref(*key), (struct apfl_value) { .type = APFL_VALUE_DICT, .dict = next_dict, } )) { return MATCH_FATAL_ERROR; } return MATCH_OK; } static enum match_result match_pattern_apply_value(apfl_ctx ctx, struct match_pattern_value *pattern_value) { if (pattern_value->var == NULL) { return false; } if (pattern_value->member_keys_len == 0) { variable_set(ctx, pattern_value->var, apfl_value_move(&pattern_value->value)); return MATCH_OK; } if (pattern_value->var->value.type != APFL_VALUE_DICT) { return MATCH_ERROR; } apfl_editable_dict ed = apfl_editable_dict_new_from_dict( ctx->allocator, apfl_dict_incref(pattern_value->var->value.dict) ); if (ed == NULL) { return MATCH_FATAL_ERROR; } enum match_result result = match_pattern_apply_value_keys( ctx->allocator, pattern_value, ed, pattern_value->member_keys_len - 1 ); if (result != MATCH_OK) { apfl_editable_dict_destroy(ed); return result; } apfl_dict d = apfl_editable_dict_finalize(ed); if (d == NULL) { return MATCH_FATAL_ERROR; } variable_set(ctx, pattern_value->var, (struct apfl_value) { .type = APFL_VALUE_DICT, .dict = d, }); return MATCH_OK; } static enum match_result match_pattern_apply(apfl_ctx ctx, struct match_pattern *pattern) { switch (pattern->type) { case MPATTERN_BLANK: return MATCH_OK; case MPATTERN_VALUE: return match_pattern_apply_value(ctx, &pattern->value); case MPATTERN_LIST: for (size_t i = 0; i < pattern->list.subpatterns_len; i++) { enum match_result result = match_pattern_apply( ctx, &pattern->list.subpatterns[i] ); if (result != MATCH_OK) { return result; } } return MATCH_OK; } assert(false); return MATCH_FATAL_ERROR; } static enum apfl_result evaluate_constant(apfl_ctx ctx, struct apfl_expr_const *constant) { return stack_push(ctx, constant_to_value(constant)) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static enum apfl_result evaluate_list_expr_to_list(apfl_ctx ctx, struct apfl_expr *expr, apfl_editable_list elist) { enum apfl_result result = evaluate(ctx, expr); if (result != APFL_RESULT_OK) { return result; } struct apfl_value value; if (!stack_pop(ctx, &value, -1)) { return APFL_RESULT_ERR_FATAL; } if (value.type != APFL_VALUE_LIST) { apfl_value_deinit(ctx->allocator, &value); return APFL_RESULT_ERR; } apfl_list list = apfl_list_incref(value.list); apfl_value_deinit(ctx->allocator, &value); if (!apfl_editable_list_append_list(elist, list)) { return APFL_RESULT_ERR_FATAL; } return APFL_RESULT_OK; } static enum apfl_result evaluate_expr_list_into_list(apfl_ctx ctx, struct apfl_expr_list *list, apfl_editable_list elist) { for (size_t i = 0; i < list->len; i++) { struct apfl_expr_list_item *item = &list->items[i]; if (item->expand) { TRY(evaluate_list_expr_to_list(ctx, item->expr, elist)); } else { TRY(evaluate(ctx, item->expr)); if (!apfl_editable_list_append(elist, stack_must_pop(ctx, -1))) { return APFL_RESULT_ERR_FATAL; } } } return APFL_RESULT_OK; } static enum apfl_result evaluate_list(apfl_ctx ctx, struct apfl_expr_list *list) { apfl_editable_list elist = apfl_editable_list_new(ctx->allocator); if (elist == NULL) { return APFL_RESULT_ERR_FATAL; } enum apfl_result result = evaluate_expr_list_into_list(ctx, list, elist); if (result != APFL_RESULT_OK) { apfl_editable_list_destroy(elist); return result; } apfl_list out = apfl_editable_list_finalize(elist); if (out == NULL) { return APFL_RESULT_ERR_FATAL; } if (!stack_push(ctx, (struct apfl_value) { .type = APFL_VALUE_LIST, .list = out, })) { apfl_list_unref(ctx->allocator, out); return APFL_RESULT_ERR_FATAL; } return APFL_RESULT_OK; } static enum apfl_result evaluate_dict_into_editable( apfl_ctx ctx, struct apfl_expr_dict *dict, apfl_editable_dict ed ) { for (size_t i = 0; i < dict->len; i++) { TRY(evaluate(ctx, dict->items[i].k)); TRY(evaluate(ctx, dict->items[i].v)); struct apfl_value value = stack_must_pop(ctx, -1); struct apfl_value key = stack_must_pop(ctx, -1); if (!apfl_editable_dict_set( ed, apfl_value_move(&key), apfl_value_move(&value) )) { return APFL_RESULT_ERR_FATAL; } } return APFL_RESULT_OK; } static enum apfl_result evaluate_dict(apfl_ctx ctx, struct apfl_expr_dict *dict) { apfl_editable_dict ed = apfl_editable_dict_new(ctx->allocator); if (ed == NULL) { return APFL_RESULT_ERR_FATAL; } enum apfl_result result = evaluate_dict_into_editable(ctx, dict, ed); if (result != APFL_RESULT_OK) { apfl_editable_dict_destroy(ed); } apfl_dict out = apfl_editable_dict_finalize(ed); if (out == NULL) { return APFL_RESULT_ERR_FATAL; } if (!stack_push(ctx, (struct apfl_value) { .type = APFL_VALUE_DICT, .dict = out, })) { apfl_dict_unref(ctx->allocator, out); return APFL_RESULT_ERR_FATAL; } return APFL_RESULT_OK; } static enum apfl_result evaluate_dot(apfl_ctx ctx, struct apfl_expr_dot *dot) { TRY(evaluate(ctx, dot->lhs)); struct apfl_value lhs = stack_must_pop(ctx, -1); struct apfl_value key = (struct apfl_value) { .type = APFL_VALUE_STRING, .string = apfl_refcounted_string_incref(dot->rhs), }; struct apfl_value out; if (apfl_value_get_item(ctx->allocator, apfl_value_move(&lhs), apfl_value_move(&key), &out) != APFL_VALUE_GET_ITEM_OK) { return APFL_RESULT_ERR; // TODO: Describe error } return stack_push(ctx, out) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static enum apfl_result evaluate_at(apfl_ctx ctx, struct apfl_expr_at *at) { TRY(evaluate(ctx, at->lhs)); TRY(evaluate(ctx, at->rhs)); struct apfl_value rhs = stack_must_pop(ctx, -1); struct apfl_value lhs = stack_must_pop(ctx, -1); struct apfl_value out; if (apfl_value_get_item(ctx->allocator, apfl_value_move(&lhs), apfl_value_move(&rhs), &out) != APFL_VALUE_GET_ITEM_OK) { return APFL_RESULT_ERR; // TODO: Describe error } return stack_push(ctx, out) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static enum apfl_result failing_match_result_to_apfl_result(enum match_result match_result) { if (match_result == MATCH_FATAL_ERROR) { return APFL_RESULT_ERR_FATAL; } return 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(ctx, pattern); return MATCH_FATAL_ERROR; } return MATCH_OK; } static enum match_result evaluate_assignment_finish( apfl_ctx ctx, struct match_pattern *pattern, struct apfl_value value ) { enum match_result match_result = match_pattern_match( ctx->allocator, pattern, apfl_value_move(&value) ); if (match_result != MATCH_OK) { return match_result; } return match_pattern_apply(ctx, pattern); } static enum apfl_result evaluate_assignment(apfl_ctx ctx, struct apfl_expr_assignment *assignment) { struct match_pattern pattern; enum match_result match_result = evaluate_assignment_setup( ctx, &pattern, assignment ); if (match_result != MATCH_OK) { return failing_match_result_to_apfl_result(match_result); } enum apfl_result result = evaluate(ctx, assignment->rhs); if (result != APFL_RESULT_OK) { match_pattern_deinit(ctx, &pattern); return result; } match_result = evaluate_assignment_finish( ctx, &pattern, stack_must_get(ctx, -1) // stack_must_get instead of stack_must_pop, // so the value is stull on the stack on return. ); match_pattern_deinit(ctx, &pattern); if (match_result != MATCH_OK) { stack_must_drop(ctx, -1); return failing_match_result_to_apfl_result(match_result); } return APFL_RESULT_OK; } static enum apfl_result evaluate_var(apfl_ctx ctx, apfl_refcounted_string varname) { variable var = ctx_get_var(ctx, varname); if (var == NULL) { return APFL_RESULT_ERR; } bool ok = stack_push(ctx, apfl_value_incref(var->value)); variable_unref(ctx, var); return ok ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; } static enum apfl_result evaluate(apfl_ctx ctx, struct apfl_expr *expr) { switch (expr->type) { case APFL_EXPR_CONSTANT: return evaluate_constant(ctx, &expr->constant); case APFL_EXPR_LIST: return evaluate_list(ctx, &expr->list); case APFL_EXPR_DICT: return evaluate_dict(ctx, &expr->dict); case APFL_EXPR_DOT: return evaluate_dot(ctx, &expr->dot); case APFL_EXPR_AT: return evaluate_at(ctx, &expr->at); case APFL_EXPR_ASSIGNMENT: return evaluate_assignment(ctx, &expr->assignment); case APFL_EXPR_VAR: return evaluate_var(ctx, apfl_refcounted_string_incref(expr->var)); case APFL_EXPR_BLANK: return stack_push(ctx, (struct apfl_value) { .type = APFL_VALUE_NIL, }) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL; case APFL_EXPR_CALL: case APFL_EXPR_SIMPLE_FUNC: case APFL_EXPR_COMPLEX_FUNC: break; // Not implemented yet } return APFL_RESULT_ERR; } enum apfl_result apfl_eval(apfl_ctx ctx, struct apfl_expr expr) { enum apfl_result result = evaluate(ctx, &expr); // TODO: expr might have been allocated with another allocator. The apfl_ctx // should probably also handle parsing and no longer accept // expressions directly. apfl_expr_deinit(ctx->allocator, &expr); return result; } void apfl_debug_print_val(apfl_ctx ctx, int index, FILE *f) { struct apfl_value value; if (!stack_pop(ctx, &value, index)) { fprintf(f, "apfl_debug_print_val: Invalid stack index %d\n", index); return; } apfl_value_print(value, f); apfl_value_deinit(ctx->allocator, &value); }