diff options
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/ast.c | 42 | ||||
-rw-r--r-- | src/tests/eval.c | 332 | ||||
-rw-r--r-- | src/tests/lexer.c | 126 | ||||
-rw-r--r-- | src/tests/parser.c | 640 | ||||
-rw-r--r-- | src/tests/slice.c | 41 |
5 files changed, 1181 insertions, 0 deletions
diff --git a/src/tests/ast.c b/src/tests/ast.c new file mode 100644 index 0000000..eb20e03 --- /dev/null +++ b/src/tests/ast.c @@ -0,0 +1,42 @@ +#include "tests/tests.h" +#include "ast.h" + +#include <string.h> + +#include "parser.h" + +static void +check_parser_errors(struct parser *parser) +{ + if (parser->errors->len > 0) { + printf("\n"); + size_t i; + char *val; + vector_foreach (parser->errors, i, val) { + printf("parser error %lu: %s\n", i, val); + } + FAIL_TEST("parser encountered errors\n"); + } +} + +static void +test_string(void) +{ + char *input = "let var = anotherVar;"; + struct parser *parser = parser_new(input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + char progbuf[1024]; + node_sprint(prog, progbuf); + asserteq(strcmp(input, progbuf), 0); + node_destroy(prog); + parser_destroy(parser); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_string); +} 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(); +} diff --git a/src/tests/lexer.c b/src/tests/lexer.c new file mode 100644 index 0000000..3700ae6 --- /dev/null +++ b/src/tests/lexer.c @@ -0,0 +1,126 @@ +#include "tests/tests.h" +#include "lexer.h" + +#include <string.h> + +#include "slice.h" +#include "token.h" + +static void +test_next_token(void) +{ + char *input = "let five = 5;\n" + "let ten = 10;\n" + "\n" + "let add = fn(x, y) {\n" + "\tx + y;\n" + "};\n" + "\n" + "let result = add(five, ten);" + "!-/*5;\n" + "5 < 10 > 5;\n" + "\n" + "if (5 < 10) {\n" + "\treturn true;\n" + "} else {\n" + "\treturn false;\n" + "}\n" + "\n" + "10 == 10;\n" + "10 != 9;\n"; + + struct lexer *lexer = lexer_new(input); + struct token expected[] = { + { TOKEN_LET, slice_fullstr("let") }, + { TOKEN_IDENT, slice_fullstr("five") }, + { TOKEN_ASSIGN, slice_fullstr("=") }, + { TOKEN_INT, slice_fullstr("5") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_LET, slice_fullstr("let") }, + { TOKEN_IDENT, slice_fullstr("ten") }, + { TOKEN_ASSIGN, slice_fullstr("=") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_LET, slice_fullstr("let") }, + { TOKEN_IDENT, slice_fullstr("add") }, + { TOKEN_ASSIGN, slice_fullstr("=") }, + { TOKEN_FUNC, slice_fullstr("fn") }, + { TOKEN_LPAREN, slice_fullstr("(") }, + { TOKEN_IDENT, slice_fullstr("x") }, + { TOKEN_COMMA, slice_fullstr(",") }, + { TOKEN_IDENT, slice_fullstr("y") }, + { TOKEN_RPAREN, slice_fullstr(")") }, + { TOKEN_LBRACE, slice_fullstr("{") }, + { TOKEN_IDENT, slice_fullstr("x") }, + { TOKEN_PLUS, slice_fullstr("+") }, + { TOKEN_IDENT, slice_fullstr("y") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_RBRACE, slice_fullstr("}") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_LET, slice_fullstr("let") }, + { TOKEN_IDENT, slice_fullstr("result") }, + { TOKEN_ASSIGN, slice_fullstr("=") }, + { TOKEN_IDENT, slice_fullstr("add") }, + { TOKEN_LPAREN, slice_fullstr("(") }, + { TOKEN_IDENT, slice_fullstr("five") }, + { TOKEN_COMMA, slice_fullstr(",") }, + { TOKEN_IDENT, slice_fullstr("ten") }, + { TOKEN_RPAREN, slice_fullstr(")") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_BANG, slice_fullstr("!") }, + { TOKEN_MINUS, slice_fullstr("-") }, + { TOKEN_SLASH, slice_fullstr("/") }, + { TOKEN_ASTERISK, slice_fullstr("*") }, + { TOKEN_INT, slice_fullstr("5") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_INT, slice_fullstr("5") }, + { TOKEN_LT, slice_fullstr("<") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_GT, slice_fullstr(">") }, + { TOKEN_INT, slice_fullstr("5") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_IF, slice_fullstr("if") }, + { TOKEN_LPAREN, slice_fullstr("(") }, + { TOKEN_INT, slice_fullstr("5") }, + { TOKEN_LT, slice_fullstr("<") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_RPAREN, slice_fullstr(")") }, + { TOKEN_LBRACE, slice_fullstr("{") }, + { TOKEN_RETURN, slice_fullstr("return") }, + { TOKEN_TRUE, slice_fullstr("true") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_RBRACE, slice_fullstr("}") }, + { TOKEN_ELSE, slice_fullstr("else") }, + { TOKEN_LBRACE, slice_fullstr("{") }, + { TOKEN_RETURN, slice_fullstr("return") }, + { TOKEN_FALSE, slice_fullstr("false") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_RBRACE, slice_fullstr("}") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_EQ, slice_fullstr("==") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_INT, slice_fullstr("10") }, + { TOKEN_NOTEQ, slice_fullstr("!=") }, + { TOKEN_INT, slice_fullstr("9") }, + { TOKEN_SEMICOLON, slice_fullstr(";") }, + { TOKEN_EOF, slice_fullstr("") }, + }; + size_t i = 0; + + do { + struct token token = lexer_next_token(lexer); + asserteq(token.type, expected[i].type); + asserteq(slice_cmp(&token.literal, &expected[i].literal), 0); + i++; + } while (expected[i].type != TOKEN_EOF); + + lexer_destroy(lexer); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_next_token); +} diff --git a/src/tests/parser.c b/src/tests/parser.c new file mode 100644 index 0000000..49847ec --- /dev/null +++ b/src/tests/parser.c @@ -0,0 +1,640 @@ +#include "parser.h" +#include "tests/tests.h" + +#include "ast.h" +#include "slice.h" + +#include <string.h> + +enum value_type { + VALUE_IDENT, + VALUE_INT, + VALUE_BOOL, +}; + +struct value { + enum value_type type; + union { + struct slice ident; + int64_t integer; + bool boolean; + }; +}; + +struct parser *parser; + +static void +check_parser_errors(struct parser *parser, const char *file, int line, + const char *func) +{ + if (parser->errors->len > 0) { + printf("\n"); + size_t i; + char *val; + vector_foreach (parser->errors, i, val) { + printf("parser error %lu: %s\n", i, val); + } + printf(TBLD TRED "FAIL!\n" TRST); + printf("%s:%d: %s: ", file, line, func); + printf("parser encountered errors\n"); + abort(); + } +} + +#define check_parser_errors(p) \ + check_parser_errors(p, __FILE__, __LINE__, __func__) + +static void +test_return_statement(struct statement *st) +{ + struct slice retrn_lit = slice_fullstr("return"); + struct slice st_lit = node_token_literal(st); + asserteq(st->type, STATEMENT_RETURN); + asserteq(slice_cmp(&retrn_lit, &st_lit), 0); +} + +static void +test_integer_literal(struct expression *expr, int64_t val) +{ + char buf[128]; + struct slice sval; + asserteq(expr->type, EXPRESSION_INT); + asserteq(expr->integer.value, val); + sprintf(buf, "%ld", val); + sval = slice_fullstr(buf); + asserteq(slice_cmp(&expr->token.literal, &sval), 0); +} + +static void +test_identifier(struct expression *expr, struct slice val) +{ + asserteq(expr->type, EXPRESSION_IDENT); +} + +static void +test_boolean_literal(struct expression *expr, bool val) +{ + char *str = val ? "true" : "false"; + struct slice sval = slice_fullstr(str); + asserteq(expr->type, EXPRESSION_BOOL); + asserteq(expr->boolean.value, val); + asserteq(slice_cmp(&expr->token.literal, &sval), 0); +} + +#define test_literal_expression(expr, expect) _Generic((expect), \ + int64_t: test_integer_literal, \ + struct slice: test_identifier, \ + bool: test_boolean_literal \ + )(expr, expect) + +static inline void +test_expected(struct expression *expr, struct value v) +{ + switch(v.type) { + case VALUE_IDENT: + test_literal_expression(expr, v.ident); + break; + case VALUE_INT: + test_literal_expression(expr, v.integer); + break; + case VALUE_BOOL: + test_literal_expression(expr, v.boolean); + break; + } +} + +#define VIDENT(v) \ + (struct value){ .type = VALUE_IDENT, .ident = slice_fullstr(v) } + +#define VINT(v) \ + (struct value){ .type = VALUE_INT, .integer = v } + +#define VBOOL(v) \ + (struct value){ .type = VALUE_BOOL, .boolean = v } + +static inline void +test_infix(struct infix_expression *expr, struct value lval, struct slice *op, + struct value rval) +{ + test_expected(expr->left, lval); + asserteq(slice_cmp(&expr->operator, op), 0); + test_expected(expr->right, rval); +} + +static void +test_let_statement(struct statement *st, struct slice *name) +{ + struct slice let_lit = slice_fullstr("let"); + struct slice st_lit = node_token_literal(st); + asserteq(slice_cmp(&let_lit, &st_lit), 0); + asserteq(st->type, STATEMENT_LET); + asserteq(slice_cmp(&st->let.name->value, name), 0); + asserteq(slice_cmp(&st->let.name->token.literal, name), 0); +} + +static void +test_let_statements(void) +{ + struct { + char *input; + struct slice ident; + struct value val; + } tests[] = { + { "let x = 5;", slice_fullstr("x"), VINT(5) }, + { "let y = true;", slice_fullstr("y"), VBOOL(true) }, + { "let foo = bar;", slice_fullstr("foo"), VIDENT("bar") }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + test_let_statement(st, &tests[i].ident); + test_expected(st->let.value, tests[i].val); + node_destroy(prog); + } + +} + +static void +test_return_statements(void) +{ + struct { + char *input; + struct slice ident; + struct value val; + } tests[] = { + { "return 5;", slice_fullstr("x"), VINT(5) }, + { "return true;", slice_fullstr("y"), VBOOL(true) }, + { "return bar;", slice_fullstr("foo"), VIDENT("bar") }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + test_return_statement(st); + test_expected(st->retrn.value, tests[i].val); + node_destroy(prog); + } +} + +static void +test_identifier_expression_statements(void) +{ + char *input = "foobar;\n"; + struct slice val = slice_fullstr("foobar"); + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + test_literal_expression(st->expr.expr, val); + + node_destroy(prog); +} + +static void +test_integer_expression_statements(void) +{ + char *input = "469;\n"; + int64_t val = 469; + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + test_literal_expression(st->expr.expr, val); + + node_destroy(prog); +} + +static void +test_prefix_expression_statements(void) +{ + struct { + char *input; + struct slice operator; + struct value val; + } tests[] = { + { "!5;", slice_fullstr("!"), VINT(5) }, + { "-15;", slice_fullstr("-"), VINT(15) }, + { "!foo;", slice_fullstr("!"), VIDENT("foo") }, + { "-bar;", slice_fullstr("-"), VIDENT("bar") }, + { "!true;", slice_fullstr("!"), VBOOL(true) }, + { "!false;", slice_fullstr("!"), VBOOL(false) }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_PREFIX); + test_expected(st->expr.expr->prefix.right, tests[i].val); + node_destroy(prog); + } +} + +static void +test_infix_expression_statements(void) +{ + struct { + char *input; + struct value lval; + struct slice operator; + struct value rval; + } tests[] = { + { "5 + 5;", VINT(5), slice_fullstr("+"), VINT(5) }, + { "5 - 5;", VINT(5), slice_fullstr("-"), VINT(5) }, + { "5 * 5;", VINT(5), slice_fullstr("*"), VINT(5) }, + { "5 / 5;", VINT(5), slice_fullstr("/"), VINT(5) }, + { "5 > 5;", VINT(5), slice_fullstr(">"), VINT(5) }, + { "5 < 5;", VINT(5), slice_fullstr("<"), VINT(5) }, + { "foo + bar;", VIDENT("foo"), slice_fullstr("+"), VIDENT("bar") }, + { "foo - bar;", VIDENT("foo"), slice_fullstr("-"), VIDENT("bar") }, + { "foo * bar;", VIDENT("foo"), slice_fullstr("*"), VIDENT("bar") }, + { "foo / bar;", VIDENT("foo"), slice_fullstr("/"), VIDENT("bar") }, + { "foo > bar;", VIDENT("foo"), slice_fullstr(">"), VIDENT("bar") }, + { "foo < bar;", VIDENT("foo"), slice_fullstr("<"), VIDENT("bar") }, + { "true == true;", VBOOL(true), slice_fullstr("=="), VBOOL(true) }, + { "true != false;", VBOOL(true), slice_fullstr("!="), VBOOL(false) }, + { "false != false;", VBOOL(false), slice_fullstr("!="), VBOOL(false) }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_INFIX); + test_infix(&st->expr.expr->infix, tests[i].lval, &tests[i].operator, + tests[i].rval); + node_destroy(prog); + } +} + +static void +test_operator_precedence(void) +{ + struct { + char *input; + char *expect; + } tests[] = { + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + char buf[128]; + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + node_sprint(prog, buf); + asserteq(strcmp(tests[i].expect, buf), 0); + node_destroy(prog); + } +} + +static void +test_boolean_expression(void) +{ + struct { + char *input; + bool value; + } tests[] = { + { "true;", true }, + { "false;", false }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + test_literal_expression(st->expr.expr, tests[i].value); + node_destroy(prog); + } +} + +static void +test_if_expression(void) +{ + char *input = "if (x < y) { x }"; + struct value x = VIDENT("x"); + struct slice op = slice_fullstr("<"); + struct value y = VIDENT("y"); + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_IF); + struct if_expression *ifexpr = &st->expr.expr->cond; + asserteq(ifexpr->condition->type, EXPRESSION_INFIX); + test_infix(&ifexpr->condition->infix, x, &op, y); + asserteq(ifexpr->consequence->type, STATEMENT_BLOCK); + asserteq(ifexpr->consequence->block.statements->len, 1); + struct statement *ifst = ifexpr->consequence->block.statements->values[0]; + asserteq(ifst->type, STATEMENT_EXPRESSION); + test_identifier(ifst->expr.expr, x.ident); + asserteq(ifexpr->alternative, NULL); + node_destroy(prog); +} + +static void +test_if_else_expression(void) +{ + char *input = "if (x < y) { x } else { y }"; + struct value x = VIDENT("x"); + struct slice op = slice_fullstr("<"); + struct value y = VIDENT("y"); + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_IF); + struct if_expression *ifexpr = &st->expr.expr->cond; + asserteq(ifexpr->condition->type, EXPRESSION_INFIX); + test_infix(&ifexpr->condition->infix, x, &op, y); + asserteq(ifexpr->consequence->type, STATEMENT_BLOCK); + asserteq(ifexpr->consequence->block.statements->len, 1); + struct statement *ifst = ifexpr->consequence->block.statements->values[0]; + asserteq(ifst->type, STATEMENT_EXPRESSION); + test_identifier(ifst->expr.expr, x.ident); + assertneq(ifexpr->alternative, NULL); + asserteq(ifexpr->alternative->block.statements->len, 1); + asserteq(ifexpr->alternative->type, STATEMENT_BLOCK); + struct statement *elsest = ifexpr->alternative->block.statements->values[0]; + asserteq(elsest->type, STATEMENT_EXPRESSION); + test_identifier(elsest->expr.expr, y.ident); + node_destroy(prog); +} + +static void +test_func_literal(void) +{ + char *input = "fn(x, y) { x + y; }"; + struct value x = VIDENT("x"); + struct slice op = slice_fullstr("+"); + struct value y = VIDENT("y"); + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_FUNC); + struct func_literal *func = &st->expr.expr->func; + asserteq(func->parameters->len, 2); + test_identifier(func->parameters->values[0], x.ident); + test_identifier(func->parameters->values[1], y.ident); + asserteq(func->body->type, STATEMENT_BLOCK); + asserteq(func->body->block.statements->len, 1); + struct statement *fst = func->body->block.statements->values[0]; + test_infix(&fst->expr.expr->infix, x, &op, y); + node_destroy(prog); +} + +static void +test_func_parameters(void) +{ + struct { + char *input; + struct { + struct slice vals[4]; + size_t len; + } params; + } tests[] = { + { + "fn() {};", + { 0 }, + }, + { + "fn(x) {};", + { + { slice_fullstr("x") }, + 1 + }, + }, + { + "fn(x, y) {};", + { + { slice_fullstr("x"), slice_fullstr("y") }, + 2, + } + }, + { + "fn(x, y, z) {};", + { + { slice_fullstr("x"), slice_fullstr("y"), slice_fullstr("z") }, + 3, + } + }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + parser_reset(parser, tests[i].input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_FUNC); + struct func_literal *func = &st->expr.expr->func; + asserteq(func->parameters->len, tests[i].params.len); + for (size_t j = 0; j < tests[i].params.len; j++) { + struct expression *param = func->parameters->values[j]; + asserteq(slice_cmp(¶m->ident.token.literal, + &tests[i].params.vals[j]), 0); + } + node_destroy(prog); + } +} + +static void +test_call_expression(void) +{ + char *input = "add(1, x * y, add(x, y, z));"; + int64_t par1 = 1; + struct value x = VIDENT("x"); + struct value y = VIDENT("y"); + struct slice op1 = slice_fullstr("*"); + parser_reset(parser, input); + struct program *prog = parser_parse_program(parser); + check_parser_errors(parser); + assertneq(prog, NULL); + asserteq(prog->statements->len, 1); + struct statement *st = prog->statements->values[0]; + asserteq(st->type, STATEMENT_EXPRESSION); + asserteq(st->expr.expr->type, EXPRESSION_CALL); + struct call_expression *call = &st->expr.expr->call; + test_identifier(call->func, slice_fullstr("add")); + asserteq(call->arguments->len, 3); + struct expression *expr1 = call->arguments->values[0], + *expr2 = call->arguments->values[1], + *expr3 = call->arguments->values[2]; + test_literal_expression(expr1, par1); + asserteq(expr2->type, EXPRESSION_INFIX); + test_infix(&expr2->infix, x, &op1, y); + asserteq(expr3->type, EXPRESSION_CALL); + asserteq(expr3->call.arguments->len, 3); +} + +static void +init(void) +{ + parser = parser_new(); +} + +static void +cleanup(void) +{ + parser_destroy(parser); +} + +int +main(void) +{ + INIT_TESTS(); + init(); + RUN_TEST(test_let_statements); + RUN_TEST(test_return_statements); + RUN_TEST(test_identifier_expression_statements); + RUN_TEST(test_integer_expression_statements); + RUN_TEST(test_prefix_expression_statements); + RUN_TEST(test_infix_expression_statements); + RUN_TEST(test_operator_precedence); + RUN_TEST(test_boolean_expression); + RUN_TEST(test_if_expression); + RUN_TEST(test_if_else_expression); + RUN_TEST(test_func_literal); + RUN_TEST(test_func_parameters); + RUN_TEST(test_call_expression); + cleanup(); +} diff --git a/src/tests/slice.c b/src/tests/slice.c new file mode 100644 index 0000000..e2382e8 --- /dev/null +++ b/src/tests/slice.c @@ -0,0 +1,41 @@ +#include "tests/tests.h" +#include "slice.h" + +#include <string.h> + +static void +test_slice_cmp(void) +{ + struct slice s1a = { + .str = "hello world", + .start = 6, + .end = 11, + }; + struct slice s1b = { + .str = "world", + .start = 0, + .end = 5, + }; + asserteq(slice_cmp(&s1a, &s1b), 0); +} + +static void +test_slice_sprint(void) +{ + struct slice slice = { + .str = "hello world", + .start = 6, + .end = 11, + }; + char buf[32]; + slice_sprint(&slice, buf); + asserteq(strcmp("world", buf), 0); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_slice_cmp); + RUN_TEST(test_slice_sprint); +} |