added some sort of error generation when parsing

errors occur
This commit is contained in:
Karma Riuk
2025-07-07 15:02:06 +02:00
parent bbac513aa9
commit bed99e0d63
4 changed files with 90 additions and 30 deletions

25
src/ast/errors/error.hpp Normal file
View File

@@ -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

View File

@@ -1,5 +1,6 @@
#include "parser.hpp" #include "parser.hpp"
#include "ast/errors/error.hpp"
#include "token/type.hpp" #include "token/type.hpp"
#include <sstream> #include <sstream>
@@ -45,6 +46,7 @@ namespace parser {
next_token(); next_token();
return true; return true;
} }
next_error(t);
return false; return false;
} }
@@ -73,6 +75,11 @@ namespace parser {
std::stringstream ss; std::stringstream ss;
ss << "Expected next token to be " << t << " but instead got " ss << "Expected next token to be " << t << " but instead got "
<< next.type; << 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 } // namespace parser

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "ast/ast.hpp" #include "ast/ast.hpp"
#include "ast/errors/error.hpp"
#include "ast/statements/let.hpp" #include "ast/statements/let.hpp"
#include "lexer/lexer.hpp" #include "lexer/lexer.hpp"
#include "token/token.hpp" #include "token/token.hpp"
@@ -8,7 +9,8 @@
namespace parser { namespace parser {
struct parser { struct parser {
parser(lexer::lexer& lexer); parser(lexer::lexer& lexer);
std::vector<std::string> errors; ~parser();
std::vector<ast::error::error*> errors;
ast::program* parse_program(); ast::program* parse_program();

View File

@@ -21,13 +21,49 @@ void test_let_statement(ast::statement* stmt, const std::string name) {
REQUIRE(let_stmt->name->token_literal() == name); REQUIRE(let_stmt->name->token_literal() == name);
} }
void checkParserErrors(const std::vector<std::string>& errors) { void test_failing_let_parsing(
std::string input_s,
std::vector<token::type> 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<ast::error::expected_next*>(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<ast::error::error*>& errors) {
if (errors.empty()) if (errors.empty())
return; return;
std::cerr << "parser has " << errors.size() << " errors:\n"; std::cerr << "parser has " << errors.size() << " errors:\n";
for (const auto& msg : errors) for (const auto& error : errors)
std::cerr << "parser error: \"" << msg << "\"\n"; std::cerr << '\t' << error->what() << "\n";
// Use doctest's FAIL macro to immediately stop // Use doctest's FAIL macro to immediately stop
FAIL_CHECK("Parser had errors. See stderr for details."); FAIL_CHECK("Parser had errors. See stderr for details.");
@@ -35,37 +71,26 @@ void checkParserErrors(const std::vector<std::string>& errors) {
TEST_CASE("Malformed let statement (checking for memory leaks)") { TEST_CASE("Malformed let statement (checking for memory leaks)") {
SUBCASE("Second token not identifier") { SUBCASE("Second token not identifier") {
std::stringstream input("\ test_failing_let_parsing("let 5 = 5;", {token::type::IDENTIFIER});
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;
} }
SUBCASE("Third token not '='") { SUBCASE("Third token not '='") {
std::stringstream input("\ test_failing_let_parsing("let five ! 5;", {token::type::ASSIGN});
let five ! 5;\ }
");
lexer::lexer l{input}; SUBCASE("Missing both identifier and '='") {
parser::parser p{l}; test_failing_let_parsing("let 5;", {token::type::IDENTIFIER});
}
ast::program* program = p.parse_program(); SUBCASE("Multiple parsing errors") {
REQUIRE_MESSAGE( test_failing_let_parsing(
program != nullptr, "let 5; let ! = 5; let five = 5; let five 5; let;",
"parse_program() returned a null pointer" {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}; parser::parser p{l};
ast::program* program = p.parse_program(); ast::program* program = p.parse_program();
check_parser_errors(p.errors);
REQUIRE_MESSAGE( REQUIRE_MESSAGE(
program != nullptr, program != nullptr,