using test suites

This commit is contained in:
Karma Riuk
2025-07-08 10:36:08 +02:00
parent ef624de4ef
commit 2dfff61346
3 changed files with 192 additions and 186 deletions

View File

@@ -6,43 +6,44 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
TEST_CASE("Single character token") { TEST_SUITE("Lexer") {
struct test { TEST_CASE("Single character token") {
token::type expectedType; struct test {
std::string expectedLiteral; token::type expectedType;
std::string expectedLiteral;
};
std::string input = "=+(){},;";
std::istringstream ss(input);
lexer::lexer l{ss};
test tests[] = {
{token::type::ASSIGN, "="},
{token::type::PLUS, "+"},
{token::type::LPAREN, "("},
{token::type::RPAREN, ")"},
{token::type::LBRACE, "{"},
{token::type::RBRACE, "}"},
{token::type::COMMA, ","},
{token::type::SEMICOLON, ";"},
{token::type::END_OF_FILE, ""},
};
for (const auto& t : tests) {
token::token tok = l.next_token();
REQUIRE(tok.type == t.expectedType);
REQUIRE(tok.literal == t.expectedLiteral);
}
}; };
std::string input = "=+(){},;"; TEST_CASE("More tokens") {
std::istringstream ss(input); struct test {
token::type expectedType;
std::string expectedLiteral;
};
lexer::lexer l{ss}; std::istringstream ss("let five = 5;\
test tests[] = {
{token::type::ASSIGN, "="},
{token::type::PLUS, "+"},
{token::type::LPAREN, "("},
{token::type::RPAREN, ")"},
{token::type::LBRACE, "{"},
{token::type::RBRACE, "}"},
{token::type::COMMA, ","},
{token::type::SEMICOLON, ";"},
{token::type::END_OF_FILE, ""},
};
for (const auto& t : tests) {
token::token tok = l.next_token();
REQUIRE(tok.type == t.expectedType);
REQUIRE(tok.literal == t.expectedLiteral);
}
};
TEST_CASE("More tokens") {
struct test {
token::type expectedType;
std::string expectedLiteral;
};
std::istringstream ss("let five = 5;\
let ten = 10;\ let ten = 10;\
let add = fn(x, y) {\ let add = fn(x, y) {\
x + y;\ x + y;\
@@ -61,101 +62,102 @@ if (5 < 10) {\
10 != 9;\ 10 != 9;\
"); ");
lexer::lexer l{ss}; lexer::lexer l{ss};
test tests[] = { test tests[] = {
// clang-format off // clang-format off
{token::type::LET, "let"}, {token::type::LET, "let"},
{token::type::IDENTIFIER, "five"}, {token::type::IDENTIFIER, "five"},
{token::type::ASSIGN, "="}, {token::type::ASSIGN, "="},
{token::type::INT, "5"}, {token::type::INT, "5"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::LET, "let"}, {token::type::LET, "let"},
{token::type::IDENTIFIER, "ten"}, {token::type::IDENTIFIER, "ten"},
{token::type::ASSIGN, "="}, {token::type::ASSIGN, "="},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::LET, "let"}, {token::type::LET, "let"},
{token::type::IDENTIFIER, "add"}, {token::type::IDENTIFIER, "add"},
{token::type::ASSIGN, "="}, {token::type::ASSIGN, "="},
{token::type::FUNCTION, "fn"}, {token::type::FUNCTION, "fn"},
{token::type::LPAREN, "("}, {token::type::LPAREN, "("},
{token::type::IDENTIFIER, "x"}, {token::type::IDENTIFIER, "x"},
{token::type::COMMA, ","}, {token::type::COMMA, ","},
{token::type::IDENTIFIER, "y"}, {token::type::IDENTIFIER, "y"},
{token::type::RPAREN, ")"}, {token::type::RPAREN, ")"},
{token::type::LBRACE, "{"}, {token::type::LBRACE, "{"},
{token::type::IDENTIFIER, "x"}, {token::type::IDENTIFIER, "x"},
{token::type::PLUS, "+"}, {token::type::PLUS, "+"},
{token::type::IDENTIFIER, "y"}, {token::type::IDENTIFIER, "y"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::RBRACE, "}"}, {token::type::RBRACE, "}"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::LET, "let"}, {token::type::LET, "let"},
{token::type::IDENTIFIER, "result"}, {token::type::IDENTIFIER, "result"},
{token::type::ASSIGN, "="}, {token::type::ASSIGN, "="},
{token::type::IDENTIFIER, "add"}, {token::type::IDENTIFIER, "add"},
{token::type::LPAREN, "("}, {token::type::LPAREN, "("},
{token::type::IDENTIFIER, "five"}, {token::type::IDENTIFIER, "five"},
{token::type::COMMA, ","}, {token::type::COMMA, ","},
{token::type::IDENTIFIER, "ten"}, {token::type::IDENTIFIER, "ten"},
{token::type::RPAREN, ")"}, {token::type::RPAREN, ")"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::BANG, "!"}, {token::type::BANG, "!"},
{token::type::MINUS, "-"}, {token::type::MINUS, "-"},
{token::type::SLASH, "/"}, {token::type::SLASH, "/"},
{token::type::ASTERISK, "*"}, {token::type::ASTERISK, "*"},
{token::type::INT, "5"}, {token::type::INT, "5"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::INT, "5"}, {token::type::INT, "5"},
{token::type::LT, "<"}, {token::type::LT, "<"},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::GT, ">"}, {token::type::GT, ">"},
{token::type::INT, "5"}, {token::type::INT, "5"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::IF, "if"}, {token::type::IF, "if"},
{token::type::LPAREN, "("}, {token::type::LPAREN, "("},
{token::type::INT, "5"}, {token::type::INT, "5"},
{token::type::LT, "<"}, {token::type::LT, "<"},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::RPAREN, ")"}, {token::type::RPAREN, ")"},
{token::type::LBRACE, "{"}, {token::type::LBRACE, "{"},
{token::type::RETURN, "return"}, {token::type::RETURN, "return"},
{token::type::TRUE, "true"}, {token::type::TRUE, "true"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::RBRACE, "}"}, {token::type::RBRACE, "}"},
{token::type::ELSE, "else"}, {token::type::ELSE, "else"},
{token::type::LBRACE, "{"}, {token::type::LBRACE, "{"},
{token::type::RETURN, "return"}, {token::type::RETURN, "return"},
{token::type::FALSE, "false"}, {token::type::FALSE, "false"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::RBRACE, "}"}, {token::type::RBRACE, "}"},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::EQ, "=="}, {token::type::EQ, "=="},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
{token::type::INT, "10"}, {token::type::INT, "10"},
{token::type::NEQ, "!="}, {token::type::NEQ, "!="},
{token::type::INT, "9"}, {token::type::INT, "9"},
{token::type::SEMICOLON, ";"}, {token::type::SEMICOLON, ";"},
// clang-format on // clang-format on
};
for (const auto& t : tests) {
token::token tok = l.next_token();
REQUIRE(tok.type == t.expectedType);
REQUIRE(tok.literal == t.expectedLiteral);
}
}; };
}
for (const auto& t : tests) {
token::token tok = l.next_token();
REQUIRE(tok.type == t.expectedType);
REQUIRE(tok.literal == t.expectedLiteral);
}
};

View File

@@ -57,66 +57,68 @@ void test_failing_let_parsing(
delete program; delete program;
} }
TEST_CASE("Malformed let statement (checking for memory leaks)") { TEST_SUITE("Parser: let") {
SUBCASE("Second token not identifier") { TEST_CASE("Malformed let statement (checking for memory leaks)") {
test_failing_let_parsing("let 5 = 5;", {token::type::IDENTIFIER}); SUBCASE("Second token not identifier") {
test_failing_let_parsing("let 5 = 5;", {token::type::IDENTIFIER});
}
SUBCASE("Third token not '='") {
test_failing_let_parsing("let five ! 5;", {token::type::ASSIGN});
}
SUBCASE("Missing both identifier and '='") {
test_failing_let_parsing("let 5;", {token::type::IDENTIFIER});
}
SUBCASE("Multiple parsing errors") {
test_failing_let_parsing(
"let 5; let ! = 5; let five = 5; let five 5; let;",
{token::type::IDENTIFIER,
token::type::IDENTIFIER,
token::type::ASSIGN,
token::type::IDENTIFIER},
1
);
}
} }
SUBCASE("Third token not '='") { TEST_CASE("Parse well formed let statements") {
test_failing_let_parsing("let five ! 5;", {token::type::ASSIGN}); std::stringstream input("\
}
SUBCASE("Missing both identifier and '='") {
test_failing_let_parsing("let 5;", {token::type::IDENTIFIER});
}
SUBCASE("Multiple parsing errors") {
test_failing_let_parsing(
"let 5; let ! = 5; let five = 5; let five 5; let;",
{token::type::IDENTIFIER,
token::type::IDENTIFIER,
token::type::ASSIGN,
token::type::IDENTIFIER},
1
);
}
}
TEST_CASE("Parse let statement") {
std::stringstream input("\
let x = 5;\ let x = 5;\
let y = 10;\ let y = 10;\
let foobar = 103213;\ let foobar = 103213;\
"); ");
lexer::lexer l{input}; lexer::lexer l{input};
parser::parser p{l}; parser::parser p{l};
ast::program* program = p.parse_program(); ast::program* program = p.parse_program();
check_parser_errors(p.errors); check_parser_errors(p.errors);
REQUIRE_MESSAGE( REQUIRE_MESSAGE(
program != nullptr, program != nullptr,
"parse_program() returned a null pointer" "parse_program() returned a null pointer"
); );
REQUIRE(program->statements.size() == 3); REQUIRE(program->statements.size() == 3);
struct test { struct test {
std::string expected_identifier; std::string expected_identifier;
}; };
test tests[]{ test tests[]{
"x", "x",
"y", "y",
"foobar", "foobar",
}; };
int i = 0; int i = 0;
for (const auto& t : tests) { for (const auto& t : tests) {
ast::statement* stmt = program->statements[i++]; ast::statement* stmt = program->statements[i++];
test_let_statement(stmt, t.expected_identifier); test_let_statement(stmt, t.expected_identifier);
}
delete program;
} }
delete program;
} }

View File

@@ -6,34 +6,36 @@
#include <doctest.h> #include <doctest.h>
#include <sstream> #include <sstream>
TEST_CASE("Parse return statement") { TEST_SUITE("Parser: return") {
std::stringstream input("\ TEST_CASE("Parse return statement") {
std::stringstream input("\
return 5;\ return 5;\
return 10;\ return 10;\
return 103213;\ return 103213;\
"); ");
lexer::lexer l{input}; lexer::lexer l{input};
parser::parser p{l}; parser::parser p{l};
ast::program* program = p.parse_program(); ast::program* program = p.parse_program();
check_parser_errors(p.errors); check_parser_errors(p.errors);
REQUIRE_MESSAGE(
program != nullptr,
"parse_program() returned a null pointer"
);
REQUIRE(program->statements.size() == 3);
for (const auto stmt : program->statements) {
REQUIRE(stmt->token_literal() == "return");
ast::return_stmt* let_stmt;
REQUIRE_NOTHROW(let_stmt = dynamic_cast<ast::return_stmt*>(stmt));
REQUIRE_MESSAGE( REQUIRE_MESSAGE(
let_stmt != nullptr, program != nullptr,
"Couldn't cast statement to a return statement" "parse_program() returned a null pointer"
); );
} REQUIRE(program->statements.size() == 3);
delete program; for (const auto stmt : program->statements) {
REQUIRE(stmt->token_literal() == "return");
ast::return_stmt* let_stmt;
REQUIRE_NOTHROW(let_stmt = dynamic_cast<ast::return_stmt*>(stmt));
REQUIRE_MESSAGE(
let_stmt != nullptr,
"Couldn't cast statement to a return statement"
);
}
delete program;
}
} }