added some sort of error generation when parsing
errors occur
This commit is contained in:
25
src/ast/errors/error.hpp
Normal file
25
src/ast/errors/error.hpp
Normal 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
|
@@ -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
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user