diff options
Diffstat (limited to 'src/tests/parser.c')
-rw-r--r-- | src/tests/parser.c | 640 |
1 files changed, 640 insertions, 0 deletions
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(); +} |