#include "eval.h" #include "tests/tests.h" #include "parser.h" #include "object.h" #include 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(); }