diff --git a/src/ast/errors/error.hpp b/src/ast/errors/error.hpp new file mode 100644 index 0000000..5057f91 --- /dev/null +++ b/src/ast/errors/error.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "token/type.hpp" + +namespace ast::error { + struct error : public std::runtime_error { + explicit error(const std::string& message) + : std::runtime_error(message) {} + }; + + struct parser_error : error { + explicit parser_error(const std::string& message): error(message) {} + }; + + struct expected_next : parser_error { + token::type expected_type; + + explicit expected_next( + token::type expected_type, const std::string& message + ) + : parser_error(message), + expected_type(expected_type) {} + }; + +} // namespace ast::error diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 220644e..cd3de65 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -1,5 +1,6 @@ #include "parser.hpp" +#include "ast/errors/error.hpp" #include "token/type.hpp" #include @@ -27,7 +28,6 @@ namespace parser { p->statements.push_back(stmt); } - return p; } @@ -45,6 +45,7 @@ namespace parser { next_token(); return true; } + next_error(t); return false; } @@ -73,6 +74,11 @@ namespace parser { std::stringstream ss; ss << "Expected next token to be " << t << " but instead got " << next.type; - errors.push_back(ss.str()); + errors.push_back(new ast::error::expected_next(t, ss.str())); + } + + parser::~parser() { + for (const auto& e : errors) + delete e; } } // namespace parser diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 6069463..a72d7f3 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -1,6 +1,7 @@ #pragma once #include "ast/ast.hpp" +#include "ast/errors/error.hpp" #include "ast/statements/let.hpp" #include "lexer/lexer.hpp" #include "token/token.hpp" @@ -8,7 +9,8 @@ namespace parser { struct parser { parser(lexer::lexer& lexer); - std::vector errors; + ~parser(); + std::vector errors; ast::program* parse_program(); diff --git a/test/parser.cpp b/test/parser.cpp index ea46c4e..98333af 100644 --- a/test/parser.cpp +++ b/test/parser.cpp @@ -21,13 +21,49 @@ void test_let_statement(ast::statement* stmt, const std::string name) { REQUIRE(let_stmt->name->token_literal() == name); } -void checkParserErrors(const std::vector& errors) { +void test_failing_let_parsing( + std::string input_s, + std::vector expected_types, + int n_good_statements = 0 +) { + std::stringstream input(input_s); + + lexer::lexer l{input}; + parser::parser p{l}; + + ast::program* program = p.parse_program(); + + // Check for errors + REQUIRE(p.errors.size() == expected_types.size()); + + int i = 0; + for (auto& e : p.errors) { + ast::error::expected_next* en; + + REQUIRE_NOTHROW(en = dynamic_cast(e)); + REQUIRE_MESSAGE( + en != nullptr, + "Couldn't cast the error to an 'expected_next'" + ); + REQUIRE(en->expected_type == expected_types[i++]); + } + + // normal program check + REQUIRE_MESSAGE( + program != nullptr, + "parse_program() returned a null pointer" + ); + REQUIRE(program->statements.size() == n_good_statements); + delete program; +} + +void check_parser_errors(const std::vector& errors) { if (errors.empty()) return; std::cerr << "parser has " << errors.size() << " errors:\n"; - for (const auto& msg : errors) - std::cerr << "parser error: \"" << msg << "\"\n"; + for (const auto& error : errors) + std::cerr << '\t' << error->what() << "\n"; // Use doctest's FAIL macro to immediately stop FAIL_CHECK("Parser had errors. See stderr for details."); @@ -35,37 +71,26 @@ void checkParserErrors(const std::vector& errors) { TEST_CASE("Malformed let statement (checking for memory leaks)") { SUBCASE("Second token not identifier") { - std::stringstream input("\ -let 5 = 5;\ -"); - - lexer::lexer l{input}; - parser::parser p{l}; - - ast::program* program = p.parse_program(); - REQUIRE_MESSAGE( - program != nullptr, - "parse_program() returned a null pointer" - ); - REQUIRE(program->statements.size() == 0); - delete program; + test_failing_let_parsing("let 5 = 5;", {token::type::IDENTIFIER}); } SUBCASE("Third token not '='") { - std::stringstream input("\ -let five ! 5;\ -"); + test_failing_let_parsing("let five ! 5;", {token::type::ASSIGN}); + } - lexer::lexer l{input}; - parser::parser p{l}; + SUBCASE("Missing both identifier and '='") { + test_failing_let_parsing("let 5;", {token::type::IDENTIFIER}); + } - ast::program* program = p.parse_program(); - REQUIRE_MESSAGE( - program != nullptr, - "parse_program() returned a null pointer" + 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 ); - REQUIRE(program->statements.size() == 0); - delete program; } } @@ -80,6 +105,7 @@ let foobar = 103213;\ parser::parser p{l}; ast::program* program = p.parse_program(); + check_parser_errors(p.errors); REQUIRE_MESSAGE( program != nullptr,