aboutsummaryrefslogtreecommitdiff
path: root/src/tests/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/parser.c')
-rw-r--r--src/tests/parser.c640
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(&param->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();
+}