using test suites
This commit is contained in:
238
test/lexer.cpp
238
test/lexer.cpp
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user