apfl/src/expr.c

1013 lines
30 KiB
C
Raw Normal View History

2021-12-10 20:22:16 +00:00
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include "apfl.h"
#include "alloc.h"
2021-12-10 20:22:16 +00:00
#include "internal.h"
void
apfl_expr_deinit(struct apfl_allocator allocator, struct apfl_expr *expr)
2021-12-10 20:22:16 +00:00
{
switch (expr->type) {
case APFL_EXPR_LIST:
apfl_expr_list_deinit(allocator, &expr->list);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_DICT:
apfl_expr_dict_deinit(allocator, &expr->dict);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_CALL:
apfl_expr_call_deinit(allocator, &expr->call);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_SIMPLE_FUNC:
2022-04-11 20:44:04 +00:00
apfl_expr_body_deinit(allocator, &expr->simple_func);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_COMPLEX_FUNC:
apfl_expr_complex_func_deinit(allocator, &expr->complex_func);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNMENT:
apfl_expr_assignment_deinit(allocator, &expr->assignment);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_DOT:
apfl_expr_dot_deinit(allocator, &expr->dot);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_AT:
apfl_expr_at_deinit(allocator, &expr->at);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_CONSTANT:
apfl_expr_const_deinit(allocator, &expr->constant);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &expr->var);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
}
void
apfl_expr_list_deinit(struct apfl_allocator allocator, struct apfl_expr_list *list)
2021-12-10 20:22:16 +00:00
{
DEINIT_LIST(allocator, list->items, list->len, apfl_expr_list_item_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_list_item_deinit(struct apfl_allocator allocator, struct apfl_expr_list_item *item)
2021-12-10 20:22:16 +00:00
{
DESTROY(allocator, item->expr, apfl_expr_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_dict_pair_deinit(struct apfl_allocator allocator, struct apfl_expr_dict_pair *pair)
2021-12-10 20:22:16 +00:00
{
DESTROY(allocator, pair->k, apfl_expr_deinit);
DESTROY(allocator, pair->v, apfl_expr_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_dict_deinit(struct apfl_allocator allocator, struct apfl_expr_dict *dict)
2021-12-10 20:22:16 +00:00
{
DEINIT_CAP_LIST(allocator, dict->items, dict->len, dict->cap, apfl_expr_dict_pair_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_call_deinit(struct apfl_allocator allocator, struct apfl_expr_call *call)
2021-12-10 20:22:16 +00:00
{
DESTROY(allocator, call->callee, apfl_expr_deinit);
apfl_expr_list_deinit(allocator, &call->arguments);
2021-12-10 20:22:16 +00:00
}
void
2022-04-11 20:44:04 +00:00
apfl_expr_body_deinit(struct apfl_allocator allocator, struct apfl_expr_body *body)
2021-12-10 20:22:16 +00:00
{
2022-04-11 20:44:04 +00:00
DEINIT_CAP_LIST(allocator, body->items, body->len, body->cap, apfl_expr_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_const_deinit(struct apfl_allocator allocator, struct apfl_expr_const *constant)
2021-12-10 20:22:16 +00:00
{
switch (constant->type) {
case APFL_EXPR_CONST_NIL:
case APFL_EXPR_CONST_BOOLEAN:
case APFL_EXPR_CONST_NUMBER:
// nop
break;
case APFL_EXPR_CONST_STRING:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &constant->string);
2021-12-10 20:22:16 +00:00
break;
}
}
#define DEINIT_GENERIC_LHS_RHS_EXPR(allocator, x, lhs_deiniter) \
do { \
DESTROY(allocator, x->lhs, lhs_deiniter); \
DESTROY(allocator, x->rhs, apfl_expr_deinit); \
2021-12-10 20:22:16 +00:00
} while (0)
void
apfl_expr_param_predicate_deinit(struct apfl_allocator allocator, struct apfl_expr_param_predicate *pred)
2021-12-10 20:22:16 +00:00
{
DEINIT_GENERIC_LHS_RHS_EXPR(allocator, pred, apfl_expr_param_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_params_deinit(struct apfl_allocator allocator, struct apfl_expr_params *params)
2021-12-10 20:22:16 +00:00
{
DEINIT_CAP_LIST(allocator, params->params, params->len, params->cap, apfl_expr_params_item_deinit);
}
void
apfl_expr_params_item_deinit(struct apfl_allocator allocator, struct apfl_expr_params_item *item)
{
apfl_expr_param_deinit(allocator, &item->param);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_param_deinit(struct apfl_allocator allocator, struct apfl_expr_param *param)
2021-12-10 20:22:16 +00:00
{
switch (param->type) {
case APFL_EXPR_PARAM_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &param->var);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_CONSTANT:
apfl_expr_const_deinit(allocator, &param->constant);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_PREDICATE:
apfl_expr_param_predicate_deinit(allocator, &param->predicate);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_LIST:
apfl_expr_params_deinit(allocator, &param->list);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
}
void
apfl_expr_subfunc_deinit(struct apfl_allocator allocator, struct apfl_expr_subfunc *subfunc)
2021-12-10 20:22:16 +00:00
{
apfl_expr_params_deinit(allocator, &subfunc->params);
2022-04-11 20:44:04 +00:00
apfl_expr_body_deinit(allocator, &subfunc->body);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_complex_func_deinit(struct apfl_allocator allocator, struct apfl_expr_complex_func *cf)
2021-12-10 20:22:16 +00:00
{
DEINIT_CAP_LIST(allocator, cf->subfuncs, cf->len, cf->cap, apfl_expr_subfunc_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_assignable_predicate_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_predicate *pred)
2021-12-10 20:22:16 +00:00
{
DEINIT_GENERIC_LHS_RHS_EXPR(allocator, pred, apfl_expr_assignable_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_assignable_list_item_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_list_item *item)
{
apfl_expr_assignable_deinit(allocator, &item->assignable);
}
2021-12-10 20:22:16 +00:00
void
apfl_expr_assignable_list_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_list *list)
2021-12-10 20:22:16 +00:00
{
DEINIT_LIST(allocator, list->items, list->len, apfl_expr_assignable_list_item_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_assignable_var_or_member_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_dot *dot)
2021-12-10 20:22:16 +00:00
{
DESTROY(allocator, dot->lhs, apfl_expr_assignable_var_or_member_deinit);
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &dot->rhs);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_assignable_var_or_member_at_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member_at *at)
{
DESTROY(allocator, at->lhs, apfl_expr_assignable_var_or_member_deinit);
DESTROY(allocator, at->rhs, apfl_expr_deinit);
}
2021-12-10 20:22:16 +00:00
void
apfl_expr_assignable_var_or_member_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable_var_or_member *var_or_member)
2021-12-10 20:22:16 +00:00
{
switch (var_or_member->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &var_or_member->var);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
apfl_expr_assignable_var_or_member_dot_deinit(allocator, &var_or_member->dot);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
apfl_expr_assignable_var_or_member_at_deinit(allocator, &var_or_member->at);
break;
}
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_assignable_deinit(struct apfl_allocator allocator, struct apfl_expr_assignable *a)
2021-12-10 20:22:16 +00:00
{
switch (a->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
apfl_expr_assignable_var_or_member_deinit(allocator, &a->var_or_member);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_CONSTANT:
apfl_expr_const_deinit(allocator, &a->constant);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_PREDICATE:
apfl_expr_assignable_predicate_deinit(allocator, &a->predicate);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_LIST:
apfl_expr_assignable_list_deinit(allocator, &a->list);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
}
void
apfl_expr_assignment_deinit(struct apfl_allocator allocator, struct apfl_expr_assignment *a)
2021-12-10 20:22:16 +00:00
{
apfl_expr_assignable_deinit(allocator, &a->lhs);
DESTROY(allocator, a->rhs, apfl_expr_deinit);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_dot_deinit(struct apfl_allocator allocator, struct apfl_expr_dot *dot)
2021-12-10 20:22:16 +00:00
{
DESTROY(allocator, dot->lhs, apfl_expr_deinit);
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
apfl_string_deinit(allocator, &dot->rhs);
2021-12-10 20:22:16 +00:00
}
void
apfl_expr_at_deinit(struct apfl_allocator allocator, struct apfl_expr_at *at)
2021-12-10 20:22:16 +00:00
{
DEINIT_GENERIC_LHS_RHS_EXPR(allocator, at, apfl_expr_deinit);
2021-12-10 20:22:16 +00:00
}
// Move functions
struct apfl_expr
apfl_expr_move(struct apfl_expr *in)
{
struct apfl_expr out = *in;
switch (in->type) {
case APFL_EXPR_LIST:
out.list = apfl_expr_list_move(&in->list);
break;
case APFL_EXPR_DICT:
out.dict = apfl_expr_dict_move(&in->dict);
break;
case APFL_EXPR_CALL:
out.call = apfl_expr_call_move(&in->call);
break;
case APFL_EXPR_SIMPLE_FUNC:
out.simple_func = apfl_expr_body_move(&in->simple_func);
break;
case APFL_EXPR_COMPLEX_FUNC:
out.complex_func = apfl_expr_complex_func_move(&in->complex_func);
break;
case APFL_EXPR_ASSIGNMENT:
out.assignment = apfl_expr_assignment_move(&in->assignment);
break;
case APFL_EXPR_DOT:
out.dot = apfl_expr_dot_move(&in->dot);
break;
case APFL_EXPR_AT:
out.at = apfl_expr_at_move(&in->at);
break;
case APFL_EXPR_CONSTANT:
out.constant = apfl_expr_const_move(&in->constant);
break;
case APFL_EXPR_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.var = apfl_string_move(&in->var);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
return out;
}
#define MOVE_LIST(out, in, items, len) \
do { \
MOVEPTR(out.items, in->items); \
out.len = in->len; \
2021-12-15 20:25:04 +00:00
in->len = 0; \
2021-12-10 20:22:16 +00:00
} while (0)
#define MOVE_CAP_LIST(out, in, items, len, cap) \
do { \
MOVEPTR(out.items, in->items); \
out.len = in->len; \
out.cap = in->cap; \
in->len = 0; \
in->cap = 0; \
} while (0)
2021-12-10 20:22:16 +00:00
struct apfl_expr_list
apfl_expr_list_move(struct apfl_expr_list *in)
{
struct apfl_expr_list out;
MOVE_LIST(out, in, items, len);
return out;
}
struct apfl_expr_list_item
apfl_expr_list_item_move(struct apfl_expr_list_item *in)
{
struct apfl_expr_list_item out = *in;
in->expr = NULL;
return out;
}
struct apfl_expr_dict_pair
apfl_expr_dict_pair_move(struct apfl_expr_dict_pair *in)
{
struct apfl_expr_dict_pair out = *in;
in->k = NULL;
in->v = NULL;
return out;
}
struct apfl_expr_dict
apfl_expr_dict_move(struct apfl_expr_dict *in)
{
struct apfl_expr_dict out;
MOVE_CAP_LIST(out, in, items, len, cap);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_call
apfl_expr_call_move(struct apfl_expr_call *in)
{
struct apfl_expr_call out;
MOVEPTR(out.callee, in->callee);
out.arguments = apfl_expr_list_move(&in->arguments);
return out;
}
2022-04-11 20:44:04 +00:00
struct apfl_expr_body
apfl_expr_body_move(struct apfl_expr_body *in)
2021-12-10 20:22:16 +00:00
{
2022-04-11 20:44:04 +00:00
struct apfl_expr_body out;
MOVE_CAP_LIST(out, in, items, len, cap);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_const
apfl_expr_const_move(struct apfl_expr_const *in)
{
struct apfl_expr_const out = *in;
switch (in->type) {
case APFL_EXPR_CONST_NIL:
case APFL_EXPR_CONST_BOOLEAN:
case APFL_EXPR_CONST_NUMBER:
// nop
break;
case APFL_EXPR_CONST_STRING:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.string = apfl_string_move(&in->string);
2021-12-10 20:22:16 +00:00
}
return out;
}
#define GENERIC_LHS_RHS_PTRS_MOVE(out, in) \
do { \
MOVEPTR(out.lhs, in->lhs); \
MOVEPTR(out.rhs, in->rhs); \
} while (0)
struct apfl_expr_param_predicate
apfl_expr_param_predicate_move(struct apfl_expr_param_predicate *in)
{
struct apfl_expr_param_predicate out;
GENERIC_LHS_RHS_PTRS_MOVE(out, in);
return out;
}
struct apfl_expr_params
apfl_expr_params_move(struct apfl_expr_params *in)
{
struct apfl_expr_params out;
MOVE_CAP_LIST(out, in, params, len, cap);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_param
apfl_expr_param_move(struct apfl_expr_param *in)
{
struct apfl_expr_param out = *in;
switch (in->type) {
case APFL_EXPR_PARAM_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.var = apfl_string_move(&in->var);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_CONSTANT:
out.constant = apfl_expr_const_move(&in->constant);
break;
case APFL_EXPR_PARAM_PREDICATE:
out.predicate = apfl_expr_param_predicate_move(&in->predicate);
break;
case APFL_EXPR_PARAM_LIST:
out.list = apfl_expr_params_move(&in->list);
break;
case APFL_EXPR_PARAM_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
return out;
}
struct apfl_expr_subfunc
apfl_expr_subfunc_move(struct apfl_expr_subfunc *in)
{
return (struct apfl_expr_subfunc) {
.params = apfl_expr_params_move(&in->params),
.body = apfl_expr_body_move(&in->body),
};
}
struct apfl_expr_complex_func
apfl_expr_complex_func_move(struct apfl_expr_complex_func *in)
{
struct apfl_expr_complex_func out;
MOVE_CAP_LIST(out, in, subfuncs, len, cap);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_assignable_var_or_member
apfl_expr_assignable_var_or_member_move(struct apfl_expr_assignable_var_or_member *in)
2021-12-10 20:22:16 +00:00
{
struct apfl_expr_assignable_var_or_member out = *in;
switch (in->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.var = apfl_string_move(&in->var);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
MOVEPTR(out.dot.lhs, in->dot.lhs);
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.dot.rhs = apfl_string_move(&in->dot.rhs);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
MOVEPTR(out.at.lhs, in->at.lhs);
MOVEPTR(out.at.rhs, in->at.rhs);
break;
}
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_assignable_predicate
apfl_expr_assignable_predicate_move(struct apfl_expr_assignable_predicate *in)
2021-12-10 20:22:16 +00:00
{
struct apfl_expr_assignable_predicate out;
2021-12-10 20:22:16 +00:00
GENERIC_LHS_RHS_PTRS_MOVE(out, in);
return out;
}
struct apfl_expr_assignable_list
apfl_expr_assignable_list_move(struct apfl_expr_assignable_list *in)
{
struct apfl_expr_assignable_list out;
MOVE_LIST(out, in, items, len);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_assignable
apfl_expr_assignable_move(struct apfl_expr_assignable *in)
{
struct apfl_expr_assignable out = *in;
switch (in->type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
out.var_or_member = apfl_expr_assignable_var_or_member_move(&in->var_or_member);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_CONSTANT:
out.constant = apfl_expr_const_move(&in->constant);
break;
case APFL_EXPR_ASSIGNABLE_PREDICATE:
out.predicate = apfl_expr_assignable_predicate_move(&in->predicate);
break;
case APFL_EXPR_ASSIGNABLE_LIST:
out.list = apfl_expr_assignable_list_move(&in->list);
break;
case APFL_EXPR_ASSIGNABLE_BLANK:
// nop
break;
2021-12-10 20:22:16 +00:00
}
return out;
}
struct apfl_expr_assignment
apfl_expr_assignment_move(struct apfl_expr_assignment *in)
{
struct apfl_expr_assignment out = *in;
out.lhs = apfl_expr_assignable_move(&in->lhs);
MOVEPTR(out.rhs, in->rhs);
return out;
}
struct apfl_expr_dot
apfl_expr_dot_move(struct apfl_expr_dot *in)
{
struct apfl_expr_dot out;
MOVEPTR(out.lhs, in->lhs);
Implement mark&sweep garbage collection and bytecode compilation Instead of the previous refcount base garbage collection, we're now using a basic tri-color mark&sweep collector. This is done to support cyclical value relationships in the future (functions can form cycles, all values implemented up to this point can not). The collector maintains a set of roots and a set of objects (grouped into blocks). The GC enabled objects are no longer allocated manually, but will be allocated by the GC. The GC also wraps an allocator, this way the GC knows, if we ran out of memory and will try to get out of this situation by performing a full collection cycle. The tri-color abstraction was chosen for two reasons: - We don't have to maintain a list of objects that need to be marked, we can simply grab the next grey one. - It should allow us to later implement incremental collection (right now we only do a stop-the-world collection). This also switches to a bytecode based evaluation of the code: We no longer directly evaluate the AST, but first compile it into a series of instructions, that are evaluated in a separate step. This was done in preparation for inplementing functions: We only need to turn a function body into instructions instead of evaluating the node again with each call of the function. Also, since an instruction list is implemented as a GC object, this then removes manual memory management of the function body and it's child nodes. Since the GC and the bytecode go hand in hand, this was done in one (giant) commit. As a downside, we've now lost the ability do do list matching on assignments. I've already started to work on implementing this in the new architecture, but left it out of this commit, as it's already quite a large commit :)
2022-04-11 20:24:22 +00:00
out.rhs = apfl_string_move(&in->rhs);
2021-12-10 20:22:16 +00:00
return out;
}
struct apfl_expr_at
apfl_expr_at_move(struct apfl_expr_at *in)
{
struct apfl_expr_at out;
GENERIC_LHS_RHS_PTRS_MOVE(out, in);
return out;
}
#define POSFMT "%d:%d"
#define POSARGS(p) (p).line, (p).col
static void print_expr(struct apfl_expr *expr, unsigned indent, FILE *f);
static void
print_expr_list(struct apfl_expr_list *list, unsigned indent, FILE *f)
{
for (size_t i = 0; i < list->len; i++) {
unsigned item_indent = indent;
if (list->items[i].expand) {
apfl_print_indented(indent, f, "Expand\n");
2021-12-10 20:22:16 +00:00
item_indent++;
}
print_expr(list->items[i].expr, item_indent, f);
}
}
static void
print_body(struct apfl_expr_body *body, unsigned indent, FILE *f)
{
for (size_t i = 0; i < body->len; i++) {
print_expr(&body->items[i], indent, f);
}
}
static void
print_constant_with_pos(struct apfl_expr_const constant, struct apfl_position pos, unsigned indent, FILE *f)
{
struct apfl_string_view sv;
switch (constant.type) {
case APFL_EXPR_CONST_NIL:
apfl_print_indented(indent, f, "Const (nil) @ " POSFMT "\n", POSARGS(pos));
break;
case APFL_EXPR_CONST_BOOLEAN:
apfl_print_indented(indent, f, "Const (%s) @ " POSFMT "\n", constant.boolean ? "true" : "false", POSARGS(pos));
break;
case APFL_EXPR_CONST_STRING:
sv = apfl_string_view_from(constant.string);
apfl_print_indented(indent, f, "Const (" APFL_STR_FMT ") @ " POSFMT "\n", APFL_STR_FMT_ARGS(sv), POSARGS(pos));
break;
case APFL_EXPR_CONST_NUMBER:
apfl_print_indented(indent, f, "Const (%f) @ " POSFMT "\n", constant.number, POSARGS(pos));
break;
}
}
2021-12-10 20:22:16 +00:00
static void
print_constant(struct apfl_expr_const constant, unsigned indent, FILE *f)
{
struct apfl_string_view sv;
2021-12-10 20:22:16 +00:00
switch (constant.type) {
case APFL_EXPR_CONST_NIL:
apfl_print_indented(indent, f, "Const (nil)\n");
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_CONST_BOOLEAN:
apfl_print_indented(indent, f, "Const (%s)\n", constant.boolean ? "true" : "false");
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_CONST_STRING:
sv = apfl_string_view_from(constant.string);
apfl_print_indented(indent, f, "Const (" APFL_STR_FMT ")\n", APFL_STR_FMT_ARGS(sv));
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_CONST_NUMBER:
apfl_print_indented(indent, f, "Const (%f)\n", constant.number);
2021-12-10 20:22:16 +00:00
break;
}
}
static void print_param(struct apfl_expr_param *, unsigned, FILE *);
static void
print_params_item(struct apfl_expr_params_item *item, unsigned indent, FILE *f)
{
if (item->expand) {
apfl_print_indented(indent, f, "Expand\n");
indent++;
}
print_param(&item->param, indent, f);
}
2021-12-10 20:22:16 +00:00
static void
print_param(struct apfl_expr_param *param, unsigned indent, FILE *f)
{
struct apfl_string_view sv;
2021-12-10 20:22:16 +00:00
switch (param->type) {
case APFL_EXPR_PARAM_VAR:
sv = apfl_string_view_from(param->var);
apfl_print_indented(indent, f, "Var (" APFL_STR_FMT ")\n", APFL_STR_FMT_ARGS(sv));
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_PARAM_CONSTANT:
print_constant(param->constant, indent, f);
break;
case APFL_EXPR_PARAM_PREDICATE:
apfl_print_indented(indent, f, "Predicate\n");
apfl_print_indented(indent+1, f, "LHS\n");
2021-12-10 20:22:16 +00:00
print_param(param->predicate.lhs, indent+2, f);
apfl_print_indented(indent+1, f, "RHS\n");
2021-12-10 20:22:16 +00:00
print_expr(param->predicate.rhs, indent+2, f);
break;
case APFL_EXPR_PARAM_LIST:
apfl_print_indented(indent, f, "List\n");
2021-12-10 20:22:16 +00:00
for (size_t i = 0; i < param->list.len; i++) {
print_params_item(&param->list.params[i], indent+1, f);
2021-12-10 20:22:16 +00:00
}
break;
case APFL_EXPR_PARAM_BLANK:
apfl_print_indented(indent, f, "Blank (_)\n");
break;
2021-12-10 20:22:16 +00:00
}
}
static void
print_assignable_var_or_member(struct apfl_expr_assignable_var_or_member var_or_member, unsigned indent, FILE *f)
{
struct apfl_string_view sv;
switch (var_or_member.type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
sv = apfl_string_view_from(var_or_member.var);
apfl_print_indented(indent, f, "Variable (" APFL_STR_FMT ")\n", APFL_STR_FMT_ARGS(sv));
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
sv = apfl_string_view_from(var_or_member.dot.rhs);
apfl_print_indented(indent, f, "Dot (" APFL_STR_FMT ")\n", APFL_STR_FMT_ARGS(sv));
print_assignable_var_or_member(*var_or_member.dot.lhs, indent+1, f);
break;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
apfl_print_indented(indent, f, "At\n");
apfl_print_indented(indent+1, f, "LHS\n");
print_assignable_var_or_member(*var_or_member.at.lhs, indent+2, f);
apfl_print_indented(indent+1, f, "RHS\n");
print_expr(var_or_member.at.rhs, indent+2, f);
break;
}
}
2021-12-10 20:22:16 +00:00
static void
print_assignable(struct apfl_expr_assignable assignable, unsigned indent, FILE *f)
{
switch(assignable.type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
print_assignable_var_or_member(assignable.var_or_member, indent, f);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_ASSIGNABLE_CONSTANT:
print_constant(assignable.constant, indent, f);
break;
case APFL_EXPR_ASSIGNABLE_PREDICATE:
apfl_print_indented(indent, f, "Predicate\n");
apfl_print_indented(indent+1, f, "LHS\n");
2021-12-10 20:22:16 +00:00
print_assignable(*assignable.predicate.lhs, indent+2, f);
apfl_print_indented(indent+1, f, "RHS\n");
2021-12-10 20:22:16 +00:00
print_expr(assignable.predicate.rhs, indent+2, f);
break;
case APFL_EXPR_ASSIGNABLE_LIST:
apfl_print_indented(indent, f, "List\n");
2021-12-10 20:22:16 +00:00
for (size_t i = 0; i < assignable.list.len; i++) {
struct apfl_expr_assignable_list_item *item = &assignable.list.items[i];
unsigned indent_item = indent+1;
if (item->expand) {
apfl_print_indented(indent_item, f, "Expand\n");
indent_item++;
}
print_assignable(item->assignable, indent_item, f);
2021-12-10 20:22:16 +00:00
}
break;
case APFL_EXPR_ASSIGNABLE_BLANK:
apfl_print_indented(indent, f, "Blank (_)\n");
break;
2021-12-10 20:22:16 +00:00
}
}
static void
print_expr(struct apfl_expr *expr, unsigned indent, FILE *f)
{
struct apfl_string_view sv;
2021-12-10 20:22:16 +00:00
switch (expr->type) {
case APFL_EXPR_LIST:
apfl_print_indented(indent, f, "List @ " POSFMT "\n", POSARGS(expr->position));
2021-12-10 20:22:16 +00:00
print_expr_list(&expr->list, indent+1, f);
break;
case APFL_EXPR_DICT:
apfl_print_indented(indent, f, "Dict @ " POSFMT "\n", POSARGS(expr->position));
2021-12-10 20:22:16 +00:00
for (size_t i = 0; i < expr->dict.len; i++) {
apfl_print_indented(indent+1, f, "Dict item\n");
apfl_print_indented(indent+2, f, "Key\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->dict.items[i].k, indent+3, f);
apfl_print_indented(indent+2, f, "Value\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->dict.items[i].v, indent+3, f);
}
break;
case APFL_EXPR_CALL:
apfl_print_indented(indent, f, "Call @ " POSFMT "\n", POSARGS(expr->position));
apfl_print_indented(indent+1, f, "Callee\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->call.callee, indent+2, f);
apfl_print_indented(indent+1, f, "Args\n");
2021-12-10 20:22:16 +00:00
print_expr_list(&expr->call.arguments, indent+2, f);
break;
case APFL_EXPR_SIMPLE_FUNC:
apfl_print_indented(indent, f, "Simple function @ " POSFMT "\n", POSARGS(expr->position));
2022-04-11 20:44:04 +00:00
print_body(&expr->simple_func, indent+1, f);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_COMPLEX_FUNC:
apfl_print_indented(indent, f, "Complex function @ " POSFMT "\n", POSARGS(expr->position));
2021-12-10 20:22:16 +00:00
for (size_t i = 0; i < expr->complex_func.len; i++) {
apfl_print_indented(indent+1, f, "Subfunction\n");
2021-12-10 20:22:16 +00:00
struct apfl_expr_subfunc *sub = &expr->complex_func.subfuncs[i];
apfl_print_indented(indent+2, f, "Parameters\n");
2021-12-10 20:22:16 +00:00
for (size_t j = 0; j < sub->params.len; j++) {
print_params_item(&sub->params.params[j], indent+3, f);
2021-12-10 20:22:16 +00:00
}
apfl_print_indented(indent+2, f, "Body\n");
2022-04-11 20:44:04 +00:00
print_body(&sub->body, indent+3, f);
2021-12-10 20:22:16 +00:00
}
break;
case APFL_EXPR_ASSIGNMENT:
apfl_print_indented(indent, f, "Assignment\n");
apfl_print_indented(indent+1, f, "LHS\n");
2021-12-10 20:22:16 +00:00
print_assignable(expr->assignment.lhs, indent+2, f);
apfl_print_indented(indent+1, f, "RHS\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->assignment.rhs, indent+2, f);
break;
case APFL_EXPR_DOT:
sv = apfl_string_view_from(expr->dot.rhs);
apfl_print_indented(indent, f, "Dot (" APFL_STR_FMT ")\n", APFL_STR_FMT_ARGS(sv));
2021-12-10 20:22:16 +00:00
print_expr(expr->dot.lhs, indent+1, f);
break;
case APFL_EXPR_AT:
apfl_print_indented(indent, f, "At\n");
apfl_print_indented(indent+1, f, "LHS\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->at.lhs, indent+2, f);
apfl_print_indented(indent+1, f, "RHS\n");
2021-12-10 20:22:16 +00:00
print_expr(expr->at.rhs, indent+2, f);
break;
case APFL_EXPR_CONSTANT:
print_constant_with_pos(expr->constant, expr->position, indent, f);
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_VAR:
sv = apfl_string_view_from(expr->var);
apfl_print_indented(indent, f, "Var (" APFL_STR_FMT ") @ " POSFMT "\n", APFL_STR_FMT_ARGS(sv), POSARGS(expr->position));
2021-12-10 20:22:16 +00:00
break;
case APFL_EXPR_BLANK:
apfl_print_indented(indent, f, "Blank (_)\n");
break;
2021-12-10 20:22:16 +00:00
}
}
void
apfl_expr_print(struct apfl_expr expr, FILE *f)
{
print_expr(&expr, 0, f);
}
static bool
expr_list_eq(struct apfl_expr_list a, struct apfl_expr_list b)
{
if (a.len != b.len) {
return false;
}
for (size_t i = 0; i < a.len; i++) {
if (
a.items[i].expand != b.items[i].expand
|| !apfl_expr_eq(*a.items[i].expr, *b.items[i].expr)
) {
return false;
}
}
return true;
}
static bool
2022-04-11 20:44:04 +00:00
body_eq(struct apfl_expr_body a, struct apfl_expr_body b)
2021-12-10 20:22:16 +00:00
{
2022-04-11 20:44:04 +00:00
if (a.len != b.len) {
return false;
}
2022-04-11 20:44:04 +00:00
for (size_t i = 0; i < a.len; i++) {
if (!apfl_expr_eq(a.items[i], b.items[i])) {
2021-12-10 20:22:16 +00:00
return false;
}
}
return true;
}
static bool param_eq(struct apfl_expr_param, struct apfl_expr_param);
static bool
params_eq(struct apfl_expr_params a, struct apfl_expr_params b)
{
if (a.len != b.len) {
return false;
}
for (size_t i = 0; i < a.len; i++) {
if (
a.params[i].expand != b.params[i].expand
|| !param_eq(a.params[i].param, b.params[i].param)
) {
2021-12-10 20:22:16 +00:00
return false;
}
}
return true;
}
static bool
const_eq(struct apfl_expr_const a, struct apfl_expr_const b)
{
if (a.type != b.type) {
return false;
}
switch (a.type) {
case APFL_EXPR_CONST_NIL:
return true;
case APFL_EXPR_CONST_BOOLEAN:
return a.boolean == b.boolean;
case APFL_EXPR_CONST_STRING:
return apfl_string_eq(a.string, b.string);
2021-12-10 20:22:16 +00:00
case APFL_EXPR_CONST_NUMBER:
return a.number == b.number;
}
assert(false);
return false;
}
static bool
param_eq(struct apfl_expr_param a, struct apfl_expr_param b)
{
if (a.type != b.type) {
return false;
}
switch (a.type) {
case APFL_EXPR_PARAM_VAR:
return apfl_string_eq(a.var, b.var);
2021-12-10 20:22:16 +00:00
case APFL_EXPR_PARAM_CONSTANT:
return const_eq(a.constant, b.constant);
case APFL_EXPR_PARAM_PREDICATE:
return param_eq(*a.predicate.lhs, *b.predicate.lhs)
&& apfl_expr_eq(*a.predicate.rhs, *b.predicate.rhs);
case APFL_EXPR_PARAM_LIST:
return params_eq(a.list, b.list);
case APFL_EXPR_PARAM_BLANK:
return true;
2021-12-10 20:22:16 +00:00
}
assert(false);
return false;
}
static bool
assignable_var_or_member_eq(struct apfl_expr_assignable_var_or_member a, struct apfl_expr_assignable_var_or_member b)
2021-12-10 20:22:16 +00:00
{
if (a.type != b.type) {
return false;
}
switch (a.type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR:
return apfl_string_eq(a.var, b.var);
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_DOT:
return assignable_var_or_member_eq(*a.dot.lhs, *b.dot.lhs)
&& apfl_string_eq(a.dot.rhs, b.dot.rhs);
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
return assignable_var_or_member_eq(*a.at.lhs, *b.at.lhs)
&& apfl_expr_eq(*a.at.rhs, *b.at.rhs);
}
assert(false);
return false;
}
static bool
assignable_eq(struct apfl_expr_assignable a, struct apfl_expr_assignable b)
{
if (a.type != b.type) {
return false;
}
switch (a.type) {
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER:
return assignable_var_or_member_eq(a.var_or_member, b.var_or_member);
2021-12-10 20:22:16 +00:00
case APFL_EXPR_ASSIGNABLE_CONSTANT:
return const_eq(a.constant, b.constant);
case APFL_EXPR_ASSIGNABLE_PREDICATE:
return assignable_eq(*a.predicate.lhs, *b.predicate.lhs)
&& apfl_expr_eq(*a.predicate.rhs, *b.predicate.rhs);
case APFL_EXPR_ASSIGNABLE_LIST:
if (a.list.len != b.list.len) {
return false;
}
for (size_t i = 0; i < a.list.len; i++) {
if (
a.list.items[i].expand != b.list.items[i].expand
|| !assignable_eq(a.list.items[i].assignable, b.list.items[i].assignable)
) {
2021-12-10 20:22:16 +00:00
return false;
}
}
return true;
case APFL_EXPR_ASSIGNABLE_BLANK:
return true;
2021-12-10 20:22:16 +00:00
}
assert(false);
return false;
}
bool
apfl_expr_eq(struct apfl_expr a, struct apfl_expr b)
{
if (a.type != b.type) {
return false;
}
2021-12-16 22:42:11 +00:00
if (!apfl_position_eq(a.position, b.position)) {
2021-12-10 20:22:16 +00:00
return false;
}
switch (a.type) {
case APFL_EXPR_LIST:
return expr_list_eq(a.list, b.list);
case APFL_EXPR_DICT:
if (a.dict.len != b.dict.len) {
return false;
}
for (size_t i = 0; i < a.dict.len; i++) {
if (
!apfl_expr_eq(*a.dict.items[i].k, *b.dict.items[i].k)
|| !apfl_expr_eq(*a.dict.items[i].v, *b.dict.items[i].v)
) {
return false;
}
}
return true;
case APFL_EXPR_CALL:
return apfl_expr_eq(*a.call.callee, *b.call.callee)
&& expr_list_eq(a.call.arguments, b.call.arguments);
case APFL_EXPR_SIMPLE_FUNC:
return body_eq(a.simple_func, b.simple_func);
case APFL_EXPR_COMPLEX_FUNC:
if (a.complex_func.len != b.complex_func.len) {
return false;
}
for (size_t i = 0; i < a.complex_func.len; i++) {
if (
!params_eq(a.complex_func.subfuncs[i].params, b.complex_func.subfuncs[i].params)
|| !body_eq(a.complex_func.subfuncs[i].body, b.complex_func.subfuncs[i].body)
) {
return false;
}
}
return true;
case APFL_EXPR_ASSIGNMENT:
return assignable_eq(a.assignment.lhs, b.assignment.lhs)
&& apfl_expr_eq(*a.assignment.rhs, *b.assignment.rhs);
case APFL_EXPR_DOT:
return apfl_expr_eq(*a.dot.lhs, *b.dot.lhs)
&& apfl_string_eq(a.dot.rhs, b.dot.rhs);
2021-12-10 20:22:16 +00:00
case APFL_EXPR_AT:
return apfl_expr_eq(*a.at.lhs, *b.at.lhs)
&& apfl_expr_eq(*a.at.rhs, *b.at.rhs);
case APFL_EXPR_CONSTANT:
return const_eq(a.constant, b.constant);
case APFL_EXPR_VAR:
return apfl_string_eq(a.var, b.var);
case APFL_EXPR_BLANK:
return true;
2021-12-10 20:22:16 +00:00
}
assert(false);
return false;
}