aboutsummaryrefslogtreecommitdiff
path: root/src/tests/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/eval.c')
-rw-r--r--src/tests/eval.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/src/tests/eval.c b/src/tests/eval.c
new file mode 100644
index 0000000..b341181
--- /dev/null
+++ b/src/tests/eval.c
@@ -0,0 +1,332 @@
+#include "eval.h"
+#include "tests/tests.h"
+
+#include "parser.h"
+#include "object.h"
+
+#include <string.h>
+
+struct parser *parser;
+
+static struct object *
+test_eval(const char *input)
+{
+ parser_reset(parser, input);
+ struct program *prog = parser_parse_program(parser);
+ struct environment *env = environment_new();
+ struct object *obj = eval(env, prog);
+ environment_destroy(env);
+ node_destroy(prog);
+
+ return obj;
+}
+
+static void
+test_eval_integer_expressions(void)
+{
+ struct {
+ char *input;
+ int64_t expect;
+ } tests[] = {
+ { "5", 5 },
+ { "69", 69 },
+ { "-5", -5 },
+ { "-69", -69 },
+ { "5 + 5 + 5 + 5 - 10", 10 },
+ { "2 * 2 * 2 * 2 * 2", 32 },
+ { "-50 + 100 + -50", 0 },
+ { "5 * 2 + 10", 20 },
+ { "5 + 2 * 10", 25 },
+ { "20 + 2 * -10", 0 },
+ { "50 / 2 * 2 + 10", 60 },
+ { "2 * (5 + 10)", 30 },
+ { "3 * 3 * 3 + 10", 37 },
+ { "3 * (3 * 3) + 10", 37 },
+ { "(5 + 10 * 2 + 15 / 3) * 2 + -10", 50 },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_INT);
+ asserteq(tests[i].expect, res->integer);
+ object_unref(res);
+ }
+}
+
+static void
+test_eval_boolean_expressions(void)
+{
+ struct {
+ char *input;
+ bool expect;
+ } tests[] = {
+ { "true", true },
+ { "false", false },
+ { "1 < 2", true },
+ { "1 > 2", false },
+ { "1 < 1", false },
+ { "1 > 1", false },
+ { "1 == 1", true },
+ { "1 != 1", false },
+ { "1 == 2", false },
+ { "1 != 2", true },
+ { "true == true", true },
+ { "false == false", true },
+ { "true == false", false },
+ { "true != false", true },
+ { "false != true", true },
+ { "(1 < 2) == true", true },
+ { "(1 < 2) == false", false },
+ { "(1 > 2) == true", false },
+ { "(1 > 2) == false", true },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_BOOL);
+ asserteq(tests[i].expect, res->boolean);
+ }
+}
+
+static void
+test_eval_bang_operators(void)
+{
+ struct {
+ char *input;
+ bool expect;
+ } tests[] = {
+ { "!true", false },
+ { "!false", true },
+ { "!5", false },
+ { "!!true", true },
+ { "!!false", false },
+ { "!!5", true },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_BOOL);
+ asserteq(tests[i].expect, res->boolean);
+ object_unref(res);
+ }
+}
+
+#define OBJ_INT(v) (struct object) { OBJECT_INT, .integer = v }
+#define OBJ_BOOL(v) (struct object) { OBJECT_BOOL, .boolean = v }
+#define OBJ_NULL() (struct object) { OBJECT_NULL, 0, }
+
+#define OBJ(v) _Generic((v), \
+ int64_t: OBJ_INT(v), \
+ int: OBJ_INT(v), \
+ bool: OBJ_BOOL(v) \
+ )
+
+static void
+test_eval_ifelse_expressions(void)
+{
+ struct {
+ char *input;
+ struct object expect;
+ } tests[] = {
+ { "if (true) { 10 }", OBJ(10) },
+ { "if (false) { 10 }", OBJ_NULL() },
+ { "if (1) { 10 }", OBJ(10) },
+ { "if (1 < 2) { 10 }", OBJ(10) },
+ { "if (1 > 2) { 10 }", OBJ_NULL() },
+ { "if (1 > 2) { 10 } else { 20 }", OBJ(20) },
+ { "if (1 < 2) { 10 } else { 20 }", OBJ(10) },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ asserteq(tests[i].expect.type, res->type);
+ asserteq(tests[i].expect.integer, res->integer);
+ object_unref(res);
+ }
+}
+
+static void
+test_eval_return_statements(void)
+{
+ struct {
+ char *input;
+ int64_t expect;
+ } tests[] = {
+ { "return 10;", 10 },
+ { "return 10; 9;", 10 },
+ { "return 2 * 5; 9;", 10 },
+ { "9; return 2 * 5; 9;", 10 },
+ {
+ "if (10 > 1) {\n"
+ "\tif(10 > 1) {\n"
+ "\t\treturn 10;\n"
+ "\t\n}"
+ "return 1;\n"
+ "}",
+ 10,
+ },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_INT);
+ asserteq(tests[i].expect, res->integer);
+ object_unref(res);
+ }
+}
+
+static void
+test_errors(void)
+{
+ struct {
+ char *input;
+ char *expect;
+ } tests[] = {
+ {
+ "5 + true;",
+ "type mismatch: int + bool",
+ },
+ {
+ "5 + true; 5;",
+ "type mismatch: int + bool",
+ },
+ {
+ "-true",
+ "unknown operator: -bool",
+ },
+ {
+ "true + false;",
+ "unknown operator: bool + bool",
+ },
+ {
+ "5; true + false; 5;",
+ "unknown operator: bool + bool",
+ },
+ {
+ "if (10 > 1) { true + false; }",
+ "unknown operator: bool + bool",
+ },
+ {
+ "if (10 > 1) {\n"
+ "\tif(10 > 1) {\n"
+ "\t\treturn true + false;\n"
+ "\t\n}"
+ "return 1;\n"
+ "}",
+ "unknown operator: bool + bool",
+ },
+ {
+ "foobar;",
+ "not declared: foobar",
+ },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_ERROR);
+ asserteq(strcmp(tests[i].expect, res->error.msg), 0);
+ object_unref(res);
+ }
+}
+
+static void
+test_eval_let_statements(void)
+{
+ struct {
+ char *input;
+ int64_t expect;
+ } tests[] = {
+ { "let a = 5; a;", 5 },
+ { "let a = 5 * 5; a;", 25 },
+ { "let a = 5; let b = a; b;", 5 },
+ { "let a = 5; let b = a; let c = a + b + 5; c;", 15 },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_INT);
+ asserteq(tests[i].expect, res->integer);
+ object_unref(res);
+ }
+}
+
+static void
+test_eval_funcs(void)
+{
+ char *input = "fn(x) { x + 2; };";
+ char *expect = "fn(x) {\n(x + 2)\n}";
+ struct object *res = test_eval(input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_FUNC);
+ char fbuf[128];
+ object_sprint(res, fbuf);
+ asserteq(strcmp(fbuf, expect), 0);
+ object_unref(res);
+}
+
+static void
+test_call_funcs(void)
+{
+ struct {
+ char *input;
+ int64_t expect;
+ } tests[] = {
+ { "let identity = fn(x) { x; }; identity(5);", 5 },
+ { "let identity = fn(x) { return x; }; identity(5);", 5 },
+ { "let double = fn(x) { x * 2; }; double(5);", 10 },
+ { "let add = fn(x, y) { x + y; }; add(5, 5);", 10 },
+ { "let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20 },
+ { "fn(x) { x; }(5)", 5 },
+ { 0 },
+ };
+
+ for (size_t i = 0; tests[i].input != NULL; i++) {
+ struct object *res = test_eval(tests[i].input);
+ assertneq(res, NULL);
+ asserteq(res->type, OBJECT_INT);
+ asserteq(tests[i].expect, res->integer);
+ object_unref(res);
+ }
+}
+
+static void
+init(void)
+{
+ parser = parser_new();
+}
+
+static void
+cleanup(void)
+{
+ parser_destroy(parser);
+}
+
+int
+main(void)
+{
+ init();
+ INIT_TESTS();
+ RUN_TEST(test_eval_integer_expressions);
+ RUN_TEST(test_eval_boolean_expressions);
+ RUN_TEST(test_eval_bang_operators);
+ RUN_TEST(test_eval_ifelse_expressions);
+ RUN_TEST(test_eval_return_statements);
+ RUN_TEST(test_errors);
+ RUN_TEST(test_eval_let_statements);
+ RUN_TEST(test_eval_funcs);
+ RUN_TEST(test_call_funcs);
+ cleanup();
+}