aboutsummaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/ast.c42
-rw-r--r--src/tests/eval.c332
-rw-r--r--src/tests/lexer.c126
-rw-r--r--src/tests/parser.c640
-rw-r--r--src/tests/slice.c41
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(&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();
+}
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);
+}