From c943380d58bb134f00d0f805aae46c86f3d8d0cc Mon Sep 17 00:00:00 2001 From: Karma Riuk Date: Sat, 19 Jul 2025 13:27:25 +0200 Subject: [PATCH] moved the utils for the parser to a global utils folder for the tests (that utils is includable only by the tests and not the src code, I added a compiler flag only for the tests in the makefile, but the compiler_flags.txt is global for the lsp, gotta be careful with that) --- Makefile | 3 +- compile_flags.txt | 1 + test/parser/expression.cpp | 107 ++++++++++++++----------- test/parser/function.cpp | 155 +++++++++++++++++++++++-------------- test/parser/if.cpp | 66 +++++++++------- test/parser/let.cpp | 24 ++++-- test/parser/return.cpp | 4 +- test/parser/utils.cpp | 126 ------------------------------ test/parser/utils.hpp | 70 ----------------- test/utils/utils.cpp | 128 ++++++++++++++++++++++++++++++ test/utils/utils.hpp | 73 +++++++++++++++++ 11 files changed, 419 insertions(+), 338 deletions(-) delete mode 100644 test/parser/utils.cpp delete mode 100644 test/parser/utils.hpp create mode 100644 test/utils/utils.cpp create mode 100644 test/utils/utils.hpp diff --git a/Makefile b/Makefile index a19d369..44424a2 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ # ------------------------------------------------------------------- CXX := g++ CXXFLAGS := -std=c++17 -Wall -Wextra -Iinclude -Isrc -MMD -MP +CXXFLAGS_TESTS := -Itest/utils LDFLAGS := SRC_DIR := src TEST_DIR := test @@ -70,7 +71,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(OBJ_DIR)/test/%.o: $(TEST_DIR)/%.cpp @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -c $< -o $@ + $(CXX) $(CXXFLAGS) $(CXXFLAGS_TESTS) -c $< -o $@ # ------------------------------------------------------------------- # Auto‐include dependencies diff --git a/compile_flags.txt b/compile_flags.txt index a6b35bb..387a99c 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,2 +1,3 @@ -I./include -I./src +-I./test/utils diff --git a/test/parser/expression.cpp b/test/parser/expression.cpp index e3328d0..8cb7b30 100644 --- a/test/parser/expression.cpp +++ b/test/parser/expression.cpp @@ -7,43 +7,49 @@ TEST_SUITE("Parser: expression") { TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Simple expression statement with identifier" ) { setup("foobar;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::identifier* ident = - cast(expression_stmt->expression); + test::utils::cast(expression_stmt->expression); REQUIRE(ident->value == "foobar"); REQUIRE(ident->token_literal() == "foobar"); }; TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Simple expression statement with integer" ) { setup("5;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); - test_integer_literal(expression_stmt->expression, 5); + test::utils::test_integer_literal(expression_stmt->expression, 5); }; - TEST_CASE_FIXTURE(ParserFixture, "Simple statements with booleans") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Simple statements with booleans" + ) { SUBCASE("True literal") { setup("true;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); - test_boolean_literal(expression_stmt->expression, true); + test::utils::test_boolean_literal( + expression_stmt->expression, + true + ); } SUBCASE("False literal") { @@ -51,9 +57,12 @@ TEST_SUITE("Parser: expression") { REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); - test_boolean_literal(expression_stmt->expression, false); + test::utils::test_boolean_literal( + expression_stmt->expression, + false + ); } SUBCASE("True let statement") { @@ -61,10 +70,10 @@ TEST_SUITE("Parser: expression") { REQUIRE(program->statements.size() == 1); ast::let_stmt* let_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); CHECK(let_stmt->name->value == "foo"); - test_boolean_literal(let_stmt->value, true); + test::utils::test_boolean_literal(let_stmt->value, true); } SUBCASE("False let statement") { @@ -72,15 +81,15 @@ TEST_SUITE("Parser: expression") { REQUIRE(program->statements.size() == 1); ast::let_stmt* let_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); CHECK(let_stmt->name->value == "bar"); - test_boolean_literal(let_stmt->value, false); + test::utils::test_boolean_literal(let_stmt->value, false); } } TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Simple expression statement with prefix before integer" ) { #define CASE(name, input, _op, _right) \ @@ -89,13 +98,13 @@ TEST_SUITE("Parser: expression") { \ REQUIRE(program->statements.size() == 1); \ ast::expression_stmt* expression_stmt = \ - cast(program->statements[0]); \ + test::utils::cast(program->statements[0]); \ \ ast::prefix_expr* prefix_expr = \ - cast(expression_stmt->expression); \ + test::utils::cast(expression_stmt->expression); \ \ REQUIRE(prefix_expr->op == _op); \ - test_integer_literal(prefix_expr->right, _right); \ + test::utils::test_integer_literal(prefix_expr->right, _right); \ } CASE("Prefix: '!'", "!5;", "!", 5); @@ -104,7 +113,7 @@ TEST_SUITE("Parser: expression") { } TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Simple expression statement with infix between integers" ) { #define CASE(name, input, _left, _op, _right) \ @@ -113,9 +122,9 @@ TEST_SUITE("Parser: expression") { REQUIRE(program->statements.size() == 1); \ \ ast::expression_stmt* expression_stmt = \ - cast(program->statements[0]); \ + test::utils::cast(program->statements[0]); \ \ - test_infix_expression( \ + test::utils::test_infix_expression( \ expression_stmt->expression, \ _left, \ _op, \ @@ -136,85 +145,91 @@ TEST_SUITE("Parser: expression") { #undef CASE } - TEST_CASE_FIXTURE(ParserFixture, "Slightly more complex infix expression") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Slightly more complex infix expression" + ) { setup("5 - -15;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::infix_expr* infix_expr = - cast(expression_stmt->expression); + test::utils::cast(expression_stmt->expression); - test_integer_literal(infix_expr->left, 5); + test::utils::test_integer_literal(infix_expr->left, 5); CHECK(infix_expr->op == "-"); ast::prefix_expr* prefix_expr = - cast(infix_expr->right); + test::utils::cast(infix_expr->right); CHECK(prefix_expr->op == "-"); - test_integer_literal(prefix_expr->right, 15); + test::utils::test_integer_literal(prefix_expr->right, 15); } TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Slightly even more complex infix expression" ) { setup("5 - -15 * 3;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::infix_expr* infix_expr = - cast(expression_stmt->expression); + test::utils::cast(expression_stmt->expression); - test_integer_literal(infix_expr->left, 5); + test::utils::test_integer_literal(infix_expr->left, 5); CHECK(infix_expr->op == "-"); ast::infix_expr* second_infix_expr = - cast(infix_expr->right); + test::utils::cast(infix_expr->right); CHECK(second_infix_expr->op == "*"); ast::prefix_expr* prefix_expr = - cast(second_infix_expr->left); + test::utils::cast(second_infix_expr->left); CHECK(prefix_expr->op == "-"); - test_integer_literal(prefix_expr->right, 15); + test::utils::test_integer_literal(prefix_expr->right, 15); - test_integer_literal(second_infix_expr->right, 3); + test::utils::test_integer_literal(second_infix_expr->right, 3); } TEST_CASE_FIXTURE( - ParserFixture, + test::utils::ParserFixture, "Checking operator precedence with simple example" ) { setup("5 * -15 - 3;"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expression_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::infix_expr* infix_expr = - cast(expression_stmt->expression); + test::utils::cast(expression_stmt->expression); ast::infix_expr* second_infix_expr = - cast(infix_expr->left); + test::utils::cast(infix_expr->left); CHECK(second_infix_expr->op == "*"); - test_integer_literal(second_infix_expr->left, 5); + test::utils::test_integer_literal(second_infix_expr->left, 5); ast::prefix_expr* prefix_expr = - cast(second_infix_expr->right); + test::utils::cast(second_infix_expr->right); CHECK(prefix_expr->op == "-"); - test_integer_literal(prefix_expr->right, 15); + test::utils::test_integer_literal(prefix_expr->right, 15); CHECK(infix_expr->op == "-"); - test_integer_literal(infix_expr->right, 3); + test::utils::test_integer_literal(infix_expr->right, 3); } - TEST_CASE_FIXTURE(ParserFixture, "Full Operator Precedence test") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Full Operator Precedence test" + ) { struct test { std::string input; std::string expected; @@ -254,7 +269,7 @@ TEST_SUITE("Parser: expression") { } } - TEST_CASE_FIXTURE(ParserFixture, "Test to see trace") { + TEST_CASE_FIXTURE(test::utils::ParserFixture, "Test to see trace") { setup("-1 * 2 - 3"); CHECK(program->str() == "(((-1) * 2) - 3)"); setup("-1 - 2 * 3"); diff --git a/test/parser/function.cpp b/test/parser/function.cpp index 5b4064f..311440b 100644 --- a/test/parser/function.cpp +++ b/test/parser/function.cpp @@ -10,77 +10,89 @@ TEST_SUITE("Parser: function literal") { TEST_CASE("Malformed function literal (checking for memory leaks)") { SUBCASE("Missing opening paren no param") { - test_failing_parsing("fn ) { return 2; }", {token::type::LPAREN}); + test::utils::test_failing_parsing( + "fn ) { return 2; }", + {token::type::LPAREN} + ); } SUBCASE("Missing opening paren one param") { - test_failing_parsing("fn x) { return 2; }", {token::type::LPAREN}); + test::utils::test_failing_parsing( + "fn x) { return 2; }", + {token::type::LPAREN} + ); } SUBCASE("Missing opening paren two param") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn x, y) { return 2; }", {token::type::LPAREN} ); } SUBCASE("Missing identifier with no closing paren - no param") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn ( { return 2; }", {token::type::IDENTIFIER} ); } SUBCASE("Missing identifier with no closing paren - one param") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x, { return 2; }", {token::type::IDENTIFIER} ); } SUBCASE("Missing identifier with no closing paren - two params") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x, y, { return 2; }", {token::type::IDENTIFIER} ); } SUBCASE("Missing identifier with closing paren - one param") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x,) { return 2; }", {token::type::IDENTIFIER} ); } SUBCASE("Missing identifier with closing paren - two params") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x,y,) { return 2; }", {token::type::IDENTIFIER} ); } SUBCASE("Missing closing paren one param") { - test_failing_parsing("fn (x { return 2; }", {token::type::RPAREN}); + test::utils::test_failing_parsing( + "fn (x { return 2; }", + {token::type::RPAREN} + ); } SUBCASE("Missing closing paren two params") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x, y { return 2; }", {token::type::RPAREN} ); } SUBCASE("Missing opening brace") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x, y) return x + y; }", {token::type::LBRACE} ); } SUBCASE("Missing closing brace") { - test_failing_parsing( + test::utils::test_failing_parsing( "fn (x, y) { return x + y; ", {token::type::RBRACE} ); } } - TEST_CASE_FIXTURE(ParserFixture, "Parse well formed function literal") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Parse well formed function literal" + ) { SUBCASE("no param function") { setup("\ fn () {\ @@ -91,10 +103,10 @@ fn () {\ CHECK(program->statements[0]->token_literal() == "fn"); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_literal* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // parameters CHECK(fun->parameters.size() == 0); @@ -102,8 +114,8 @@ fn () {\ // block REQUIRE(fun->body->statements.size() == 1); ast::return_stmt* ret = - cast(fun->body->statements[0]); - test_boolean_literal(ret->value, true); + test::utils::cast(fun->body->statements[0]); + test::utils::test_boolean_literal(ret->value, true); // full string CHECK(fun->str() == "fn (){return true;}"); @@ -119,20 +131,20 @@ fn (x) {\ CHECK(program->statements[0]->token_literal() == "fn"); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_literal* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // parameters REQUIRE(fun->parameters.size() == 1); - test_identifier(fun->parameters[0], "x"); + test::utils::test_identifier(fun->parameters[0], "x"); // block REQUIRE(fun->body->statements.size() == 1); ast::return_stmt* ret = - cast(fun->body->statements[0]); - test_infix_expression(ret->value, "x", "+", 1); + test::utils::cast(fun->body->statements[0]); + test::utils::test_infix_expression(ret->value, "x", "+", 1); // full string CHECK(fun->str() == "fn (x){return (x + 1);}"); @@ -148,21 +160,21 @@ fn (x, y) {\ CHECK(program->statements[0]->token_literal() == "fn"); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_literal* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // parameters REQUIRE(fun->parameters.size() == 2); - test_identifier(fun->parameters[0], "x"); - test_identifier(fun->parameters[1], "y"); + test::utils::test_identifier(fun->parameters[0], "x"); + test::utils::test_identifier(fun->parameters[1], "y"); // block REQUIRE(fun->body->statements.size() == 1); ast::return_stmt* ret = - cast(fun->body->statements[0]); - test_infix_expression(ret->value, "x", "+", "y"); + test::utils::cast(fun->body->statements[0]); + test::utils::test_infix_expression(ret->value, "x", "+", "y"); // full string CHECK(fun->str() == "fn (x, y){return (x + y);}"); @@ -178,26 +190,26 @@ let fun = fn (x, y) {\ CHECK(program->statements[0]->token_literal() == "let"); ast::let_stmt* let_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); // let lhs - test_identifier(let_stmt->name, "fun"); + test::utils::test_identifier(let_stmt->name, "fun"); // let rhs CHECK(let_stmt->value->token_literal() == "fn"); ast::function_literal* fun = - cast(let_stmt->value); + test::utils::cast(let_stmt->value); // parameters REQUIRE(fun->parameters.size() == 2); - test_identifier(fun->parameters[0], "x"); - test_identifier(fun->parameters[1], "y"); + test::utils::test_identifier(fun->parameters[0], "x"); + test::utils::test_identifier(fun->parameters[1], "y"); // block REQUIRE(fun->body->statements.size() == 1); ast::return_stmt* ret = - cast(fun->body->statements[0]); - test_infix_expression(ret->value, "x", "+", "y"); + test::utils::cast(fun->body->statements[0]); + test::utils::test_infix_expression(ret->value, "x", "+", "y"); // full string CHECK(fun->str() == "fn (x, y){return (x + y);}"); @@ -219,10 +231,10 @@ TEST_SUITE("Parser: function call") { // Check for errors REQUIRE(p.errors.size() == 2); ast::error::unkown_prefix* up = - cast(p.errors[0]); + test::utils::cast(p.errors[0]); REQUIRE(up->prefix.type == token::type::SEMICOLON); ast::error::expected_next* en = - cast(p.errors[1]); + test::utils::cast(p.errors[1]); REQUIRE(en->expected_type == token::type::RPAREN); @@ -233,31 +245,43 @@ TEST_SUITE("Parser: function call") { ); } SUBCASE("missing closing 1 param function") { - test_failing_parsing("value(x;", {token::type::RPAREN}); + test::utils::test_failing_parsing( + "value(x;", + {token::type::RPAREN} + ); } SUBCASE("missing closing 2 param function") { - test_failing_parsing("value(x, y;", {token::type::RPAREN}); + test::utils::test_failing_parsing( + "value(x, y;", + {token::type::RPAREN} + ); } SUBCASE("missing comma between 2 param function") { - test_failing_parsing("value(x y);", {token::type::RPAREN}); + test::utils::test_failing_parsing( + "value(x y);", + {token::type::RPAREN} + ); } } - TEST_CASE_FIXTURE(ParserFixture, "Parse well formed function call") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Parse well formed function call" + ) { SUBCASE("no param function") { setup("value();"); REQUIRE(program->statements.size() == 1); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_call* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // target - test_identifier(fun->target, "value"); + test::utils::test_identifier(fun->target, "value"); // parameters CHECK(fun->parameters.size() == 0); @@ -271,17 +295,17 @@ TEST_SUITE("Parser: function call") { REQUIRE(program->statements.size() == 1); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_call* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // target - test_identifier(fun->target, "is_odd"); + test::utils::test_identifier(fun->target, "is_odd"); // parameters CHECK(fun->parameters.size() == 1); - test_integer_literal(fun->parameters[0], 1); + test::utils::test_integer_literal(fun->parameters[0], 1); // full string CHECK(fun->str() == "is_odd(1)"); @@ -292,18 +316,23 @@ TEST_SUITE("Parser: function call") { REQUIRE(program->statements.size() == 1); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::function_call* fun = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // target - test_identifier(fun->target, "is_gt"); + test::utils::test_identifier(fun->target, "is_gt"); // parameters CHECK(fun->parameters.size() == 2); - test_integer_literal(fun->parameters[0], 1); - test_infix_expression(fun->parameters[1], "a", "+", 10); + test::utils::test_integer_literal(fun->parameters[0], 1); + test::utils::test_infix_expression( + fun->parameters[1], + "a", + "+", + 10 + ); // full string CHECK(fun->str() == "is_gt(1, (a + 10))"); @@ -314,20 +343,26 @@ TEST_SUITE("Parser: function call") { REQUIRE(program->statements.size() == 1); ast::let_stmt* let_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); - test_identifier(let_stmt->name, "res"); + test::utils::test_identifier(let_stmt->name, "res"); - ast::function_call* fun = cast(let_stmt->value); + ast::function_call* fun = + test::utils::cast(let_stmt->value); // target - test_identifier(fun->target, "add"); + test::utils::test_identifier(fun->target, "add"); // parameters CHECK(fun->parameters.size() == 3); - test_integer_literal(fun->parameters[0], 1); - test_infix_expression(fun->parameters[1], "a", "+", 10); - test_infix_expression(fun->parameters[2], 2, "*", 3); + test::utils::test_integer_literal(fun->parameters[0], 1); + test::utils::test_infix_expression( + fun->parameters[1], + "a", + "+", + 10 + ); + test::utils::test_infix_expression(fun->parameters[2], 2, "*", 3); // full string CHECK(fun->str() == "add(1, (a + 10), (2 * 3))"); diff --git a/test/parser/if.cpp b/test/parser/if.cpp index 283e326..762e6ae 100644 --- a/test/parser/if.cpp +++ b/test/parser/if.cpp @@ -9,7 +9,7 @@ TEST_SUITE("Parser: if") { TEST_CASE("Malformed if then else (checking for memory leaks)") { SUBCASE("Missing opening paren") { - test_failing_parsing( + test::utils::test_failing_parsing( "if x > 15) {\ return x;\ }", @@ -18,7 +18,7 @@ TEST_SUITE("Parser: if") { } SUBCASE("Missing closing paren") { - test_failing_parsing( + test::utils::test_failing_parsing( "if (x > 15 {\ return x;\ }", @@ -27,7 +27,7 @@ TEST_SUITE("Parser: if") { } SUBCASE("Missing opening brace") { - test_failing_parsing( + test::utils::test_failing_parsing( "if (x > 15) \ return x;\ }", @@ -36,7 +36,7 @@ TEST_SUITE("Parser: if") { } SUBCASE("Missing closing brace") { - test_failing_parsing( + test::utils::test_failing_parsing( "if (x > 15) { \ return x;\ ", @@ -45,7 +45,10 @@ TEST_SUITE("Parser: if") { } } - TEST_CASE_FIXTURE(ParserFixture, "Parse well formed simple if ") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Parse well formed simple if " + ) { setup("\ if (x > 15) {\ return x;\ @@ -55,21 +58,23 @@ if (x > 15) {\ CHECK(program->statements[0]->token_literal() == "if"); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::if_then_else* if_stmt = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // condition - ast::infix_expr* cond = cast(if_stmt->condition); - test_infix_expression(cond, "x", ">", 15); + ast::infix_expr* cond = + test::utils::cast(if_stmt->condition); + test::utils::test_infix_expression(cond, "x", ">", 15); CHECK(if_stmt->condition->str() == "(x > 15)"); // consequence REQUIRE(if_stmt->consequence->statements.size() == 1); - ast::return_stmt* ret_stmt = - cast(if_stmt->consequence->statements[0]); - test_identifier(ret_stmt->value, "x"); + ast::return_stmt* ret_stmt = test::utils::cast( + if_stmt->consequence->statements[0] + ); + test::utils::test_identifier(ret_stmt->value, "x"); CHECK(if_stmt->consequence->str() == "{return x;}"); // alternative @@ -79,7 +84,10 @@ if (x > 15) {\ CHECK(if_stmt->str() == "if (x > 15){return x;}"); } - TEST_CASE_FIXTURE(ParserFixture, "Parse well formed if then else") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Parse well formed if then else" + ) { setup("\ if (x < 5) {\ return 15;\ @@ -92,34 +100,38 @@ if (x < 5) {\ CHECK(program->statements[0]->token_literal() == "if"); ast::expression_stmt* expr_stmt = - cast(program->statements[0]); + test::utils::cast(program->statements[0]); ast::if_then_else* if_stmt = - cast(expr_stmt->expression); + test::utils::cast(expr_stmt->expression); // condition - ast::infix_expr* cond = cast(if_stmt->condition); - test_infix_expression(cond, "x", "<", 5); + ast::infix_expr* cond = + test::utils::cast(if_stmt->condition); + test::utils::test_infix_expression(cond, "x", "<", 5); CHECK(if_stmt->condition->str() == "(x < 5)"); // consequence REQUIRE(if_stmt->consequence->statements.size() == 1); - ast::return_stmt* ret_stmt = - cast(if_stmt->consequence->statements[0]); - test_integer_literal(ret_stmt->value, 15); + ast::return_stmt* ret_stmt = test::utils::cast( + if_stmt->consequence->statements[0] + ); + test::utils::test_integer_literal(ret_stmt->value, 15); CHECK(if_stmt->consequence->str() == "{return 15;}"); // alternative REQUIRE(if_stmt->alternative != nullptr); REQUIRE(if_stmt->alternative->statements.size() == 2); - ast::let_stmt* let_stmt = - cast(if_stmt->alternative->statements[0]); - test_identifier(let_stmt->name, "b"); - test_integer_literal(let_stmt->value, 1); + ast::let_stmt* let_stmt = test::utils::cast( + if_stmt->alternative->statements[0] + ); + test::utils::test_identifier(let_stmt->name, "b"); + test::utils::test_integer_literal(let_stmt->value, 1); - ast::return_stmt* ret_stmt2 = - cast(if_stmt->alternative->statements[1]); - test_identifier(ret_stmt2->value, "x"); + ast::return_stmt* ret_stmt2 = test::utils::cast( + if_stmt->alternative->statements[1] + ); + test::utils::test_identifier(ret_stmt2->value, "x"); CHECK(if_stmt->alternative->str() == "{let b = 1;return x;}"); // full string diff --git a/test/parser/let.cpp b/test/parser/let.cpp index 9cfb624..ba83bdc 100644 --- a/test/parser/let.cpp +++ b/test/parser/let.cpp @@ -8,7 +8,7 @@ void test_let_statement(ast::statement* stmt, const std::string name) { REQUIRE(stmt->token_literal() == "let"); - ast::let_stmt* let_stmt = cast(stmt); + ast::let_stmt* let_stmt = test::utils::cast(stmt); REQUIRE(let_stmt->name->value == name); REQUIRE(let_stmt->name->token_literal() == name); @@ -17,19 +17,28 @@ void test_let_statement(ast::statement* stmt, const std::string name) { TEST_SUITE("Parser: let") { TEST_CASE("Malformed let statement (checking for memory leaks)") { SUBCASE("Second token not identifier") { - test_failing_parsing("let 5 = 5;", {token::type::IDENTIFIER}); + test::utils::test_failing_parsing( + "let 5 = 5;", + {token::type::IDENTIFIER} + ); } SUBCASE("Third token not '='") { - test_failing_parsing("let five ! 5;", {token::type::ASSIGN}); + test::utils::test_failing_parsing( + "let five ! 5;", + {token::type::ASSIGN} + ); } SUBCASE("Missing both identifier and '='") { - test_failing_parsing("let 5;", {token::type::IDENTIFIER}); + test::utils::test_failing_parsing( + "let 5;", + {token::type::IDENTIFIER} + ); } SUBCASE("Multiple parsing errors") { - test_failing_parsing( + test::utils::test_failing_parsing( "let 5; let ! = 5; let five = 5; let five 5; let;", {token::type::IDENTIFIER, token::type::IDENTIFIER, @@ -40,7 +49,10 @@ TEST_SUITE("Parser: let") { } } - TEST_CASE_FIXTURE(ParserFixture, "Parse well formed let statements") { + TEST_CASE_FIXTURE( + test::utils::ParserFixture, + "Parse well formed let statements" + ) { setup("\ let x = 5;\ let y = 10;\ diff --git a/test/parser/return.cpp b/test/parser/return.cpp index 90cf0cf..1e51a6f 100644 --- a/test/parser/return.cpp +++ b/test/parser/return.cpp @@ -3,7 +3,7 @@ #include TEST_SUITE("Parser: return") { - TEST_CASE_FIXTURE(ParserFixture, "Parse return statement") { + TEST_CASE_FIXTURE(test::utils::ParserFixture, "Parse return statement") { setup("\ return 5;\ return 10;\ @@ -15,7 +15,7 @@ return 12 + 34;\ for (const auto stmt : program->statements) { CHECK(stmt->token_literal() == "return"); - cast(stmt); + test::utils::cast(stmt); } } } diff --git a/test/parser/utils.cpp b/test/parser/utils.cpp deleted file mode 100644 index 908e3c5..0000000 --- a/test/parser/utils.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "utils.hpp" - -#include "ast/expressions/boolean.hpp" -#include "ast/expressions/identifier.hpp" -#include "ast/expressions/infix.hpp" -#include "ast/expressions/integer.hpp" - -#include - -void check_parser_errors(const std::vector& errors) { - if (errors.empty()) - return; - - INFO("parser has " << errors.size() << " errors:"); - std::ostringstream combined; - for (auto& err : errors) - combined << " > " << err->what() << '\n'; - - INFO(combined.str()); - - FAIL("Parser had errors."); -} - -void ParserFixture::setup(std::string source) { - input.clear(); - input << source; - lexer = std::make_unique(input); - parser = std::make_unique(*lexer); - program = parser->parse_program(); - check_parser_errors(parser->errors); - - REQUIRE_MESSAGE( - program != nullptr, - "parse_program() returned a null pointer" - ); -} - -void test_identifier(ast::expression* expr, std::string value) { - ast::identifier* ident = cast(expr); - - REQUIRE(ident->value == value); - REQUIRE(ident->token_literal() == value); -} - -void test_integer_literal(ast::expression* expr, int value) { - ast::integer_literal* int_lit = cast(expr); - - REQUIRE(int_lit->value == value); - std::ostringstream oss; - oss << value; - REQUIRE(int_lit->token_literal() == oss.str()); -} - -void test_boolean_literal(ast::expression* expr, bool value) { - ast::boolean_literal* bool_lit = cast(expr); - - REQUIRE(bool_lit->value == value); - std::ostringstream oss; - oss << (value ? "true" : "false"); - REQUIRE(bool_lit->token_literal() == oss.str()); -} - -void test_literal_expression(ast::expression* exp, std::any& expected) { - if (expected.type() == typeid(int)) - return test_integer_literal(exp, std::any_cast(expected)); - if (expected.type() == typeid(bool)) - return test_boolean_literal(exp, std::any_cast(expected)); - if (expected.type() == typeid(std::string)) - return test_identifier(exp, std::any_cast(expected)); - if (expected.type() == typeid(const char*)) - return test_identifier(exp, std::any_cast(expected)); - - FAIL( - "Type of exp not handled. Got: " * demangle(typeid(*exp).name()) - * " as expression and " * demangle(expected.type().name()) - * " as expected" - ); -} - -void test_infix_expression( - ast::expression* exp, std::any left, std::string op, std::any right -) { - ast::infix_expr* op_exp = cast(exp); - test_literal_expression(op_exp->left, left); - CHECK(op_exp->op == op); - test_literal_expression(op_exp->right, right); -} - -void test_failing_parsing( - std::string input_s, - std::vector expected_types, - int n_good_statements -) { - std::stringstream input(input_s); - - lexer::lexer l{input}; - parser::parser p{l}; - - std::unique_ptr program = p.parse_program(); - INFO(*program); - - // Check for errors - REQUIRE(p.errors.size() >= expected_types.size()); - // ^^ because even though you were thinking - // about a specific token to be there, other `expect_next -> false` might be - // triggered for subexpressions due to the first one - - int i = 0; - for (auto& t : expected_types) { - ast::error::expected_next* en = - cast(p.errors[i++]); - - REQUIRE(en->expected_type == t); - } - - // normal program check - REQUIRE_MESSAGE( - program != nullptr, - "parse_program() returned a null pointer" - ); - REQUIRE(program->statements.size() >= n_good_statements); - // ^^ because even though you were thinking - // about a specific number of statements to be there, it failing for - // `expect_next` might trigger a sub-expression to be triggered correctly - // and be parsed as the expression_stmt -} diff --git a/test/parser/utils.hpp b/test/parser/utils.hpp deleted file mode 100644 index 8cad691..0000000 --- a/test/parser/utils.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "ast/ast.hpp" -#include "lexer/lexer.hpp" -#include "parser/parser.hpp" - -#include -#include -#include -#include -#include - -void check_parser_errors(const std::vector&); - -namespace { - - std::string demangle(const char* name) { - int status = 0; - std::unique_ptr demangled( - abi::__cxa_demangle(name, nullptr, nullptr, &status), - &std::free - ); - return (status == 0 && demangled) ? demangled.get() : name; - } - - template - T* cast_impl(Base* base) { - static_assert( - std::is_base_of_v, - "T must be derived from Base" - ); - - T* t; - REQUIRE_NOTHROW(t = dynamic_cast(base)); - REQUIRE_MESSAGE( - t != nullptr, - "Couldn't cast " * demangle(typeid(*base).name()) * " (given as " - * demangle(typeid(Base).name()) * ")" * " to a " - * demangle(typeid(T).name()) - ); - return t; - } -} // namespace - -// Overloads for your known base types -template -T* cast(ast::node* stmt) { - return cast_impl(stmt); -} - -template -T* cast(ast::error::error* err) { - return cast_impl(err); -} - -struct ParserFixture { - std::stringstream input; - std::unique_ptr lexer; - std::unique_ptr parser; - std::unique_ptr program; - - ParserFixture() = default; - - void setup(std::string); -}; - -void test_identifier(ast::expression*, std::string); -void test_integer_literal(ast::expression*, int); -void test_boolean_literal(ast::expression*, bool); -void test_literal_expression(ast::expression*, std::any&); -void test_infix_expression(ast::expression*, std::any, std::string, std::any); -void test_failing_parsing(std::string, std::vector, int = 0); diff --git a/test/utils/utils.cpp b/test/utils/utils.cpp new file mode 100644 index 0000000..a8eb7a9 --- /dev/null +++ b/test/utils/utils.cpp @@ -0,0 +1,128 @@ +#include "utils.hpp" + +#include "ast/expressions/boolean.hpp" +#include "ast/expressions/identifier.hpp" +#include "ast/expressions/infix.hpp" +#include "ast/expressions/integer.hpp" + +#include + +namespace test::utils { + void check_parser_errors(const std::vector& errors) { + if (errors.empty()) + return; + + INFO("parser has " << errors.size() << " errors:"); + std::ostringstream combined; + for (auto& err : errors) + combined << " > " << err->what() << '\n'; + + INFO(combined.str()); + + FAIL("Parser had errors."); + } + + void ParserFixture::setup(std::string source) { + input.clear(); + input << source; + lexer = std::make_unique(input); + parser = std::make_unique(*lexer); + program = parser->parse_program(); + check_parser_errors(parser->errors); + + REQUIRE_MESSAGE( + program != nullptr, + "parse_program() returned a null pointer" + ); + } + + void test_identifier(ast::expression* expr, std::string value) { + ast::identifier* ident = cast(expr); + + REQUIRE(ident->value == value); + REQUIRE(ident->token_literal() == value); + } + + void test_integer_literal(ast::expression* expr, int value) { + ast::integer_literal* int_lit = cast(expr); + + REQUIRE(int_lit->value == value); + std::ostringstream oss; + oss << value; + REQUIRE(int_lit->token_literal() == oss.str()); + } + + void test_boolean_literal(ast::expression* expr, bool value) { + ast::boolean_literal* bool_lit = cast(expr); + + REQUIRE(bool_lit->value == value); + std::ostringstream oss; + oss << (value ? "true" : "false"); + REQUIRE(bool_lit->token_literal() == oss.str()); + } + + void test_literal_expression(ast::expression* exp, std::any& expected) { + if (expected.type() == typeid(int)) + return test_integer_literal(exp, std::any_cast(expected)); + if (expected.type() == typeid(bool)) + return test_boolean_literal(exp, std::any_cast(expected)); + if (expected.type() == typeid(std::string)) + return test_identifier(exp, std::any_cast(expected)); + if (expected.type() == typeid(const char*)) + return test_identifier(exp, std::any_cast(expected)); + + FAIL( + "Type of exp not handled. Got: " * demangle(typeid(*exp).name()) + * " as expression and " * demangle(expected.type().name()) + * " as expected" + ); + } + + void test_infix_expression( + ast::expression* exp, std::any left, std::string op, std::any right + ) { + ast::infix_expr* op_exp = cast(exp); + test_literal_expression(op_exp->left, left); + CHECK(op_exp->op == op); + test_literal_expression(op_exp->right, right); + } + + void test_failing_parsing( + std::string input_s, + std::vector expected_types, + int n_good_statements + ) { + std::stringstream input(input_s); + + lexer::lexer l{input}; + parser::parser p{l}; + + std::unique_ptr program = p.parse_program(); + INFO(*program); + + // Check for errors + REQUIRE(p.errors.size() >= expected_types.size()); + // ^^ because even though you were thinking + // about a specific token to be there, other `expect_next -> false` + // might be triggered for subexpressions due to the first one + + int i = 0; + for (auto& t : expected_types) { + ast::error::expected_next* en = + cast(p.errors[i++]); + + REQUIRE(en->expected_type == t); + } + + // normal program check + REQUIRE_MESSAGE( + program != nullptr, + "parse_program() returned a null pointer" + ); + REQUIRE(program->statements.size() >= n_good_statements); + // ^^ because even though you were thinking + // about a specific number of statements to be there, it failing for + // `expect_next` might trigger a sub-expression to be triggered + // correctly and be parsed as the expression_stmt + } +} // namespace test::utils diff --git a/test/utils/utils.hpp b/test/utils/utils.hpp new file mode 100644 index 0000000..85a5ae2 --- /dev/null +++ b/test/utils/utils.hpp @@ -0,0 +1,73 @@ +#include "ast/ast.hpp" +#include "lexer/lexer.hpp" +#include "parser/parser.hpp" + +#include +#include +#include +#include +#include + +namespace test::utils { + void check_parser_errors(const std::vector&); + + namespace { + + std::string demangle(const char* name) { + int status = 0; + std::unique_ptr demangled( + abi::__cxa_demangle(name, nullptr, nullptr, &status), + &std::free + ); + return (status == 0 && demangled) ? demangled.get() : name; + } + + template + T* cast_impl(Base* base) { + static_assert( + std::is_base_of_v, + "T must be derived from Base" + ); + + T* t; + REQUIRE_NOTHROW(t = dynamic_cast(base)); + REQUIRE_MESSAGE( + t != nullptr, + "Couldn't cast " * demangle(typeid(*base).name()) + * " (given as " * demangle(typeid(Base).name()) * ")" + * " to a " * demangle(typeid(T).name()) + ); + return t; + } + } // namespace + + // Overloads for your known base types + template + T* cast(ast::node* stmt) { + return cast_impl(stmt); + } + + template + T* cast(ast::error::error* err) { + return cast_impl(err); + } + + struct ParserFixture { + std::stringstream input; + std::unique_ptr lexer; + std::unique_ptr parser; + std::unique_ptr program; + + ParserFixture() = default; + + void setup(std::string); + }; + + void test_identifier(ast::expression*, std::string); + void test_integer_literal(ast::expression*, int); + void test_boolean_literal(ast::expression*, bool); + void test_literal_expression(ast::expression*, std::any&); + void + test_infix_expression(ast::expression*, std::any, std::string, std::any); + void test_failing_parsing(std::string, std::vector, int = 0); +} // namespace test::utils