From f9878b43d8d592035c8c1b6a00958cf61b65452b Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sun, 20 Nov 2022 13:47:38 +0100 Subject: [PATCH] Implement comparison operators --- src/CMakeLists.txt | 1 + src/apfl.h | 4 +++ src/context.c | 26 ++++++++++++++++ src/functional-tests/compare.at | 22 ++++++++++++++ src/globals.c | 30 ++++++++++++++++++ src/messages.c | 2 ++ src/value.c | 54 +++++++++++++++++++++++++++++++++ src/value.h | 10 ++++++ 8 files changed, 149 insertions(+) create mode 100644 src/functional-tests/compare.at diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c03f377..17e391a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,6 +69,7 @@ functionaltest("chained-assignments") functionaltest("dictionary-assignments") functionaltest("variadic-functions") functionaltest("predicate") +functionaltest("compare") install(TARGETS apfl DESTINATION lib) install(TARGETS apfl-bin DESTINATION bin) diff --git a/src/apfl.h b/src/apfl.h index 14b085a..556b265 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -698,6 +698,8 @@ bool apfl_is_truthy(apfl_ctx, apfl_stackidx); apfl_number apfl_get_number(apfl_ctx, apfl_stackidx); // Pops two values from the stack and returns whether they are equal. bool apfl_eq(apfl_ctx, apfl_stackidx, apfl_stackidx); +// Pops two values from the stack and compares them (a < b: -1; a = b: 0; a > b: 1) +int apfl_cmp(apfl_ctx, apfl_stackidx a, apfl_stackidx b); // Push a C function onto the stack with nslots slots (initialized to nil) void apfl_push_cfunc(apfl_ctx, apfl_cfunc, size_t nslots); @@ -746,6 +748,8 @@ struct apfl_messages { const char *value_doesnt_match; const char *invalid_matcher_state; const char *no_matching_subfunction; + const char *uncomparable; + const char *incompatible_types; }; extern const struct apfl_messages apfl_messages; diff --git a/src/context.c b/src/context.c index 6fdf28a..d321547 100644 --- a/src/context.c +++ b/src/context.c @@ -1251,6 +1251,32 @@ apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b) return eq; } +int +apfl_cmp(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b) +{ + struct apfl_value a = apfl_stack_must_get(ctx, _a); + struct apfl_value b = apfl_stack_must_get(ctx, _b); + + enum comparison_result result = apfl_value_cmp(a, b); + assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b})); + + switch (result) { + case CMP_LT: + return -1; + case CMP_EQ: + return 0; + case CMP_GT: + return 1; + case CMP_UNCOMPARABLE: + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.uncomparable); + case CMP_INCOMPATIBLE_TYPES: + apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.incompatible_types); + } + + assert(false); + return 0; +} + static struct scope * closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes) { diff --git a/src/functional-tests/compare.at b/src/functional-tests/compare.at new file mode 100644 index 0000000..5a56d26 --- /dev/null +++ b/src/functional-tests/compare.at @@ -0,0 +1,22 @@ +===== script ===== +compare = { ~args -> + print ~args "==>" (> ~args) (>= ~args) (< ~args) (<= ~args) +} + + +compare 1 2 +compare 1 1 +compare "foo" "bar" +compare 1 2 3 +compare 1 3 2 +compare nil nil +compare true false + +===== output ===== +1 2 ==> false false true true +1 1 ==> false true false true +foo bar ==> true true false false +1 2 3 ==> false false true true +1 3 2 ==> false false false false +nil nil ==> false true false true +true false ==> true true false false diff --git a/src/globals.c b/src/globals.c index d234588..8168bd4 100644 --- a/src/globals.c +++ b/src/globals.c @@ -81,6 +81,32 @@ impl_eq(apfl_ctx ctx) apfl_push_bool(ctx, true); } +#define IMPLEMENT_COMPARISON(impl_name, name, cmp) \ + static void \ + impl_name(apfl_ctx ctx) \ + { \ + size_t len = apfl_len(ctx, 0); \ + if (len < 2) { \ + apfl_raise_const_error(ctx, APFL_RESULT_ERR, name " needs at least 2 arguments"); \ + } \ + \ + for (size_t i = 0; i < len-1; i++) { \ + apfl_get_list_member_by_index(ctx, 0, i); \ + apfl_get_list_member_by_index(ctx, 0, i+1); \ + if (!(apfl_cmp(ctx, -2, -1) cmp 0)) { \ + apfl_push_bool(ctx, false); \ + return; \ + } \ + } \ + \ + apfl_push_bool(ctx, true); \ + } \ + +IMPLEMENT_COMPARISON(impl_gt, ">", >) +IMPLEMENT_COMPARISON(impl_lt, "<", <) +IMPLEMENT_COMPARISON(impl_ge, ">=", >=) +IMPLEMENT_COMPARISON(impl_le, "<=", <=) + #define IMPL_MATH_OP(name, op) \ static apfl_number \ name(apfl_ctx ctx, apfl_number a, apfl_number b) \ @@ -332,6 +358,10 @@ impl_gc(apfl_ctx ctx) static const struct global_def globals[] = { {"if", impl_if}, {"==", impl_eq}, + {">", impl_gt}, + {"<", impl_lt}, + {">=", impl_ge}, + {"<=", impl_le}, {"+", impl_plus}, {"-", impl_minus}, {"*", impl_mult}, diff --git a/src/messages.c b/src/messages.c index 3353cfa..ed5dcf9 100644 --- a/src/messages.c +++ b/src/messages.c @@ -20,4 +20,6 @@ const struct apfl_messages apfl_messages = { .value_doesnt_match = "Value does not match", .invalid_matcher_state = "Matcher is in invalid state", .no_matching_subfunction = "No matching subfunction", + .uncomparable = "Value is not comparable", + .incompatible_types = "Incompatible types", }; diff --git a/src/value.c b/src/value.c index ab58932..bf4e171 100644 --- a/src/value.c +++ b/src/value.c @@ -367,6 +367,60 @@ apfl_value_eq(const struct apfl_value a, const struct apfl_value b) return false; } +#define CMP(a, b) (((a) == (b)) ? CMP_EQ : (((a) < (b)) ? CMP_LT : CMP_GT)) + +enum comparison_result +apfl_value_cmp(const struct apfl_value a, const struct apfl_value b) +{ + switch (a.type) { + case VALUE_NIL: + if (b.type != VALUE_NIL) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP_EQ; + case VALUE_BOOLEAN: + if (b.type != VALUE_BOOLEAN) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP(a.boolean ? 1 : 0, b.boolean ? 1 : 0); + case VALUE_NUMBER: + if (b.type != VALUE_NUMBER) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP(a.number, b.number); + case VALUE_STRING: + case VALUE_CONST_STRING: + if (b.type != VALUE_STRING && b.type != VALUE_CONST_STRING) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP(apfl_string_cmp(as_string_view(a), as_string_view(b)), 0); + case VALUE_LIST: + if (b.type != VALUE_LIST) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP_UNCOMPARABLE; + case VALUE_DICT: + if (b.type != VALUE_DICT) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP_UNCOMPARABLE; + case VALUE_FUNC: + case VALUE_CFUNC: + if (b.type != VALUE_FUNC && b.type != VALUE_CFUNC) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP_UNCOMPARABLE; + case VALUE_USERDATA: + if (b.type != VALUE_USERDATA) { + return CMP_INCOMPATIBLE_TYPES; + } + return CMP_UNCOMPARABLE; + } + + assert(false); + return CMP_INCOMPATIBLE_TYPES; +} + struct list_header * apfl_list_new(struct gc *gc, size_t initial_cap) { diff --git a/src/value.h b/src/value.h index 115ca04..546c9ed 100644 --- a/src/value.h +++ b/src/value.h @@ -86,6 +86,16 @@ bool apfl_value_format(struct apfl_value, struct apfl_format_writer); bool apfl_value_print(struct apfl_value, struct apfl_format_writer); apfl_hash apfl_value_hash(const struct apfl_value); +enum comparison_result { + CMP_LT, + CMP_EQ, + CMP_GT, + CMP_UNCOMPARABLE, + CMP_INCOMPATIBLE_TYPES, +}; + +enum comparison_result apfl_value_cmp(const struct apfl_value a, const struct apfl_value b); + enum get_item_result { GET_ITEM_OK, GET_ITEM_KEY_DOESNT_EXIST,