Error reporting system improvement

This commit is contained in:
Robert Bendun 2022-05-29 22:39:46 +02:00
parent df236b13c8
commit 605533de32
16 changed files with 330 additions and 229 deletions

View File

@ -79,7 +79,10 @@ def run_tests(file_paths: list):
return_code = 0 return_code = 0
for program_file in file_paths: for program_file in file_paths:
test_case_file = find_path_for_test_case(program_file) test_case_file = find_path_for_test_case(program_file)
tc = Test_Case.from_file(test_case_file) if os.path.exists(test_case_file) else Test_Case() if os.path.exists(test_case_file):
tc = Test_Case.from_file(test_case_file)
else:
continue
flags_list = [Interpreter] flags_list = [Interpreter]
if hasattr(tc, "flags"): if hasattr(tc, "flags"):

View File

@ -1,6 +1,6 @@
{ {
"returncode": 0, "returncode": 0,
"stdout": "4\n30\n42\nnil\n", "stdout": "4\n30\n42\n",
"stderr": "", "stderr": "",
"flags": [] "flags": []
} }

View File

@ -0,0 +1,6 @@
{
"returncode": 0,
"stdout": "1\n2\n3\n4\n5\n",
"stderr": "",
"flags": []
}

View File

@ -1,6 +1,6 @@
{ {
"returncode": 0, "returncode": 0,
"stdout": "100\n200\n120\nnil\n", "stdout": "100\n200\n120\n",
"stderr": "", "stderr": "",
"flags": [] "flags": []
} }

View File

@ -1,6 +1,6 @@
{ {
"returncode": 0, "returncode": 0,
"stdout": "1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n3628800\nnil\n", "stdout": "1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n3628800\n",
"stderr": "", "stderr": "",
"flags": [] "flags": []
} }

View File

@ -0,0 +1,6 @@
{
"returncode": 0,
"stdout": "",
"stderr": "",
"flags": []
}

View File

@ -1,6 +1,6 @@
{ {
"returncode": 0, "returncode": 0,
"stdout": "11\nnil\n", "stdout": "11\n",
"stderr": "", "stderr": "",
"flags": [] "flags": []
} }

View File

@ -3,9 +3,20 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
bool Error::operator==(errors::Type type) template<typename T, typename ...Params>
concept Callable = requires(T t, Params ...params) { t(params...); };
template<typename Lambda, typename ...T>
concept Visitor = (Callable<Lambda, T> && ...);
/// Custom visit function for better C++ compilation error messages
template<typename V, typename ...T>
static auto visit(V &&visitor, std::variant<T...> const& variant)
{ {
return this->type == type; static_assert(Visitor<V, T...>, "visitor must cover all types");
if constexpr (Visitor<V, T...>) {
return std::visit(std::forward<V>(visitor), variant);
}
} }
Error Error::with(Location loc) && Error Error::with(Location loc) &&
@ -17,183 +28,138 @@ Error Error::with(Location loc) &&
enum class Error_Level enum class Error_Level
{ {
Error, Error,
Notice,
Bug Bug
}; };
static std::ostream& error_heading(std::ostream &os, std::optional<Location> location, Error_Level lvl) static std::ostream& error_heading(
std::ostream &os,
std::optional<Location> location,
Error_Level lvl,
std::string_view short_description)
{ {
if (location) {
os << *location;
} else {
os << "musique";
}
switch (lvl) { switch (lvl) {
case Error_Level::Error: return os << ": error: "; case Error_Level::Error:
case Error_Level::Notice: return os << ": notice: "; os << "ERROR " << short_description << ' ';
break;
// This branch should be reached if we have Error_Level::Bug // This branch should be reached if we have Error_Level::Bug
// or definetely where error level is outside of enum Error_Level // or definetely where error level is outside of enum Error_Level
default: return os << ": implementation bug: "; default:
os << "IMPLEMENTATION BUG " << short_description << ' ';
} }
if (location) {
os << " at " << *location;
}
return os << '\n';
}
static void encourage_contact(std::ostream &os)
{
os <<
"Interpreter got in state that was not expected by it's developers.\n"
"Contact them providing code that coused it and error message above to resolve this trouble\n"
<< std::flush;
} }
void assert(bool condition, std::string message, Location loc) void assert(bool condition, std::string message, Location loc)
{ {
if (condition) return; if (condition) return;
error_heading(std::cerr, loc, Error_Level::Bug) << message << std::endl; error_heading(std::cerr, loc, Error_Level::Bug, "Assertion in interpreter");
if (not message.empty())
std::cerr << "with message: " << message << std::endl;
encourage_contact(std::cerr);
std::exit(1); std::exit(1);
} }
[[noreturn]] void unimplemented(std::string_view message, Location loc) [[noreturn]] void unimplemented(std::string_view message, Location loc)
{ {
if (message.empty()) { error_heading(std::cerr, loc, Error_Level::Bug, "This part of interpreter was not implemented yet");
error_heading(std::cerr, loc, Error_Level::Bug) << "this part was not implemented yet" << std::endl;
} else { if (not message.empty()) {
error_heading(std::cerr, loc, Error_Level::Bug) << message << std::endl; std::cerr << message << std::endl;
} }
encourage_contact(std::cerr);
std::exit(1); std::exit(1);
} }
[[noreturn]] void unreachable(Location loc) [[noreturn]] void unreachable(Location loc)
{ {
error_heading(std::cerr, loc, Error_Level::Bug) << "this place should not be reached" << std::endl;
error_heading(std::cerr, loc, Error_Level::Bug, "Reached unreachable state");
encourage_contact(std::cerr);
std::exit(1); std::exit(1);
} }
std::ostream& operator<<(std::ostream& os, Error const& err) std::ostream& operator<<(std::ostream& os, Error const& err)
{ {
error_heading(os, err.location, Error_Level::Error); std::string_view short_description = visit(Overloaded {
[](errors::Expected_Keyword const&) { return "Expected keyword"; },
[](errors::Failed_Numeric_Parsing const&) { return "Failed to parse a number"; },
[](errors::Music_Literal_Used_As_Identifier const&) { return "Music literal in place of identifier"; },
[](errors::Not_Callable const&) { return "Value not callable"; },
[](errors::Undefined_Identifier const&) { return "Undefined identifier"; },
[](errors::Undefined_Operator const&) { return "Undefined operator"; },
[](errors::Unexpected_Empty_Source const&) { return "Unexpected end of file"; },
[](errors::Unexpected_Keyword const&) { return "Unexpected keyword"; },
[](errors::Unrecognized_Character const&) { return "Unrecognized character"; },
[](errors::internal::Unexpected_Token const&) { return "Unexpected token"; }
}, err.details);
switch (err.type) { error_heading(os, err.location, Error_Level::Error, short_description);
case errors::End_Of_File:
return os << "end of file\n";
case errors::Unrecognized_Character: visit(Overloaded {
return err.message.empty() ? os << "unrecognized character\n" : os << err.message; [&os](errors::Unrecognized_Character const& err) {
os << "I encountered character in the source code that was not supposed to be here.\n";
os << " Character Unicode code: " << std::hex << err.invalid_character << '\n';
os << " Character printed: '" << utf8::Print{err.invalid_character} << "'\n";
os << "\n";
os << "Musique only accepts characters that are unicode letters or ascii numbers and punctuation\n";
},
case errors::Expected_Keyword: [&os](errors::Failed_Numeric_Parsing const& err) {
case errors::Function_Not_Defined: constexpr auto Max = std::numeric_limits<decltype(Number::num)>::max();
case errors::Not_Callable: constexpr auto Min = std::numeric_limits<decltype(Number::num)>::min();
case errors::Unexpected_Token_Type: os << "I tried to parse numeric literal, but I failed.";
case errors::Unresolved_Operator: if (err.reason == std::errc::result_out_of_range) {
return os << err.message << '\n'; os << " Declared number is outside of valid range of numbers that can be represented.\n";
os << " Only numbers in range [" << Min << ", " << Max << "] are supported\n";
}
},
case errors::Unexpected_Empty_Source: [&os](errors::internal::Unexpected_Token const& ut) {
return os << "unexpected end of input\n"; os << "I encountered unexpected token during " << ut.when << '\n';
os << " Token type: " << ut.type << '\n';
os << " Token source: " << ut.source << '\n';
case errors::Failed_Numeric_Parsing: os << "\nThis error is considered an internal one. It should not be displayed to the end user.\n";
return err.error_code == std::errc::result_out_of_range encourage_contact(os);
? os << "number " << err.message << " cannot be represented with " << (sizeof(Number::num)*8) << " bit number\n" },
: os << "couldn't parse number " << err.message << '\n';
}
return os << "unrecognized error type\n"; [&os](errors::Expected_Keyword const&) {},
} [&os](errors::Music_Literal_Used_As_Identifier const&) {},
[&os](errors::Not_Callable const&) {},
[&os](errors::Undefined_Identifier const&) {},
[&os](errors::Undefined_Operator const&) {},
[&os](errors::Unexpected_Keyword const&) {},
[&os](errors::Unexpected_Empty_Source const&) {}
}, err.details);
static std::string format(auto const& ...args) return os;
{
std::stringstream ss;
(void) (ss << ... << args);
return ss.str();
}
Error errors::unrecognized_character(u32 invalid_character)
{
Error err;
err.type = errors::Unrecognized_Character;
err.message = format(
"unrecognized charater U+",
std::hex, invalid_character,
" (char: '", utf8::Print{invalid_character}, "')");
return err;
}
Error errors::unrecognized_character(u32 invalid_character, Location location)
{
return unrecognized_character(invalid_character).with(std::move(location));
}
Error errors::unexpected_token(Token const& unexpected)
{
Error err;
err.type = errors::Unexpected_Token_Type;
err.location = unexpected.location;
err.message = format("unexpected ", unexpected.type);
return err;
}
Error errors::unexpected_token(Token::Type expected, Token const& unexpected)
{
Error err;
err.type = errors::Unexpected_Token_Type;
err.location = unexpected.location;
err.message = format("expected ", expected, ", but got ", unexpected.type);
return err;
}
Error errors::unexpected_end_of_source(Location location)
{
Error err;
err.type = errors::Unexpected_Empty_Source;
err.location = location;
return err;
}
Error errors::failed_numeric_parsing(Location location, std::errc errc, std::string_view source)
{
Error err;
err.type = errors::Failed_Numeric_Parsing;
err.location = location;
err.error_code = errc;
err.message = source;
return err;
}
Error errors::function_not_defined(Value const& value)
{
Error err;
err.type = Function_Not_Defined;
err.message = "Function '" + value.s + "' has not been defined yet";
return err;
}
Error errors::unresolved_operator(Token const& op)
{
Error err;
err.type = errors::Unresolved_Operator;
err.location = op.location;
err.message = format("Unresolved operator '", op.source, "'");
return err;
}
Error errors::expected_keyword(Token const& unexpected, std::string_view keyword)
{
Error err;
err.type = errors::Expected_Keyword;
err.location = unexpected.location;
err.message = format("Expected keyword '", keyword, "', but found ", unexpected);
return err;
}
Error errors::not_callable(std::optional<Location> location, Value::Type value_type)
{
Error err;
err.type = errors::Not_Callable;
err.location = std::move(location);
err.message = format("Couldn't call value of type ", type_name(value_type));
return err;
} }
void errors::all_tokens_were_not_parsed(std::span<Token> tokens) void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
{ {
error_heading(std::cerr, std::nullopt, Error_Level::Bug); error_heading(std::cerr, std::nullopt, Error_Level::Bug, "Remaining tokens");
std::cerr << "remaining tokens after parsing. Listing remaining tokens:\n"; std::cerr << "remaining tokens after parsing. Listing remaining tokens:\n";
for (auto const& token : tokens) { for (auto const& token : tokens) {
error_heading(std::cerr, token.location, Error_Level::Notice);
std::cerr << token << '\n'; std::cerr << token << '\n';
} }

View File

@ -374,7 +374,10 @@ Result<Value> Interpreter::eval(Ast &&ast)
auto op = operators.find(std::string(ast.token.source)); auto op = operators.find(std::string(ast.token.source));
if (op == operators.end()) { if (op == operators.end()) {
return errors::unresolved_operator(ast.token); return Error {
.details = errors::Undefined_Operator { .op = ast.token.source },
.location = ast.token.location
};
} }
for (auto& a : ast.arguments) { for (auto& a : ast.arguments) {

View File

@ -60,13 +60,13 @@ void Lexer::skip_whitespace_and_comments()
} }
} }
auto Lexer::next_token() -> Result<Token> auto Lexer::next_token() -> Result<std::variant<Token, End_Of_File>>
{ {
skip_whitespace_and_comments(); skip_whitespace_and_comments();
start(); start();
if (peek() == 0) { if (peek() == 0) {
return errors::End_Of_File; return End_Of_File{};
} }
switch (peek()) { switch (peek()) {
@ -135,7 +135,10 @@ auto Lexer::next_token() -> Result<Token>
return Token { Token::Type::Operator, finish(), token_location }; return Token { Token::Type::Operator, finish(), token_location };
} }
return errors::unrecognized_character(peek(), token_location); return Error {
.details = errors::Unrecognized_Character { .invalid_character = peek() },
.location = token_location
};
} }
auto Lexer::peek() const -> u32 auto Lexer::peek() const -> u32

View File

@ -45,22 +45,86 @@ using isize = std::ptrdiff_t;
/// as first class feature in Zig programming language. /// as first class feature in Zig programming language.
namespace errors namespace errors
{ {
enum Type struct Unrecognized_Character
{ {
End_Of_File, u32 invalid_character;
Unrecognized_Character,
Unexpected_Token_Type,
Unexpected_Empty_Source,
Failed_Numeric_Parsing,
Function_Not_Defined,
Unresolved_Operator,
Expected_Keyword,
Not_Callable
}; };
struct Unexpected_Empty_Source
{
};
struct Failed_Numeric_Parsing
{
std::errc reason;
};
struct Undefined_Identifier
{
};
struct Expected_Keyword
{
std::string_view keyword;
std::string_view received_type = {};
};
struct Unexpected_Keyword
{
std::string_view keyword;
};
struct Undefined_Operator
{
std::string_view op;
};
struct Not_Callable
{
std::string_view type;
};
struct Music_Literal_Used_As_Identifier
{
std::string_view source;
/// Why only identifier can be used?
std::string_view identifier_context;
};
/// Collection of messages that are considered internal and should not be printed to the end user.
namespace internal
{
struct Unexpected_Token
{
/// Type of the token
std::string_view type;
/// Source of the token
std::string_view source;
/// Where this token was encountered that was unexpected?
std::string_view when;
};
}
using Details = std::variant<
Expected_Keyword,
Failed_Numeric_Parsing,
Music_Literal_Used_As_Identifier,
Not_Callable,
Undefined_Identifier,
Undefined_Operator,
Unexpected_Empty_Source,
Unexpected_Keyword,
Unrecognized_Character,
internal::Unexpected_Token
>;
} }
template<typename ...Lambdas>
struct Overloaded : Lambdas... { using Lambdas::operator()...; };
/// \brief Location describes code position in `file line column` format. /// \brief Location describes code position in `file line column` format.
/// It's used both to represent position in source files provided /// It's used both to represent position in source files provided
// to interpreter and internal interpreter usage. // to interpreter and internal interpreter usage.
@ -113,12 +177,9 @@ void assert(bool condition, std::string message, Location loc = Location::caller
struct Error struct Error
{ {
errors::Type type; errors::Details details;
std::optional<Location> location = std::nullopt; std::optional<Location> location = std::nullopt;
std::string message{};
std::errc error_code{};
bool operator==(errors::Type);
Error with(Location) &&; Error with(Location) &&;
}; };
@ -152,11 +213,6 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i
{ {
} }
constexpr Result(errors::Type error)
: Storage(tl::unexpect, Error { error } )
{
}
inline Result(Error error) inline Result(Error error)
: Storage(tl::unexpected(std::move(error))) : Storage(tl::unexpected(std::move(error)))
{ {
@ -300,6 +356,9 @@ std::ostream& operator<<(std::ostream& os, Token const& tok);
/// Token type debug printing /// Token type debug printing
std::ostream& operator<<(std::ostream& os, Token::Type type); std::ostream& operator<<(std::ostream& os, Token::Type type);
/// Explicit marker of the end of file
struct End_Of_File {};
/// Lexer takes source code and turns it into list of tokens /// Lexer takes source code and turns it into list of tokens
/// ///
/// It allows for creating sequence of tokens by using next_token() method. /// It allows for creating sequence of tokens by using next_token() method.
@ -332,8 +391,8 @@ struct Lexer
/// Used only for rewinding /// Used only for rewinding
Location prev_location{}; Location prev_location{};
/// Try to tokenize next token /// Try to tokenize next token.
auto next_token() -> Result<Token>; auto next_token() -> Result<std::variant<Token, End_Of_File>>;
/// Skip whitespace and comments from the beggining of the source /// Skip whitespace and comments from the beggining of the source
/// ///
@ -475,9 +534,6 @@ struct Parser
/// Tests if current token has given type and source /// Tests if current token has given type and source
bool expect(Token::Type type, std::string_view lexeme) const; bool expect(Token::Type type, std::string_view lexeme) const;
/// Ensures that current token has one of types given, otherwise returns error
Result<void> ensure(Token::Type type) const;
}; };
/// Number type supporting integer and fractional constants /// Number type supporting integer and fractional constants
@ -787,21 +843,6 @@ struct Interpreter
namespace errors namespace errors
{ {
Error unrecognized_character(u32 invalid_character);
Error unrecognized_character(u32 invalid_character, Location location);
Error unexpected_token(Token::Type expected, Token const& unexpected);
Error unexpected_token(Token const& unexpected);
Error unexpected_end_of_source(Location location);
Error failed_numeric_parsing(Location location, std::errc errc, std::string_view source);
Error function_not_defined(Value const& v);
Error unresolved_operator(Token const& op);
Error expected_keyword(Token const& unexpected, std::string_view keyword);
Error not_callable(std::optional<Location> location, Value::Type value_type);
[[noreturn]] [[noreturn]]
void all_tokens_were_not_parsed(std::span<Token>); void all_tokens_were_not_parsed(std::span<Token>);
} }

View File

@ -165,7 +165,10 @@ Result<Number> Number::from(Token token)
num_end = begin; num_end = begin;
goto parse_fractional; goto parse_fractional;
} }
return errors::failed_numeric_parsing(std::move(token.location), ec, token.source); return Error {
.details = errors::Failed_Numeric_Parsing { .reason = ec },
.location = std::move(token.location)
};
} }
parsed_numerator = true; parsed_numerator = true;
@ -177,7 +180,10 @@ parse_fractional:
if (ec != std::errc{}) { if (ec != std::errc{}) {
if (parsed_numerator && ec == std::errc::invalid_argument && token.source != ".") if (parsed_numerator && ec == std::errc::invalid_argument && token.source != ".")
return result; return result;
return errors::failed_numeric_parsing(std::move(token.location), ec, token.source); return Error {
.details = errors::Failed_Numeric_Parsing { .reason = ec },
.location = std::move(token.location)
};
} }
result += Number{ frac, pow10(frac_end - num_end) }; result += Number{ frac, pow10(frac_end - num_end) };
} }

View File

@ -27,12 +27,14 @@ Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
lexer.location.filename = filename; lexer.location.filename = filename;
Parser parser; Parser parser;
for (;;) if (auto maybe_token = lexer.next_token(); maybe_token.has_value()) { for (;;) {
parser.tokens.emplace_back(*std::move(maybe_token)); auto token_or_eof = Try(lexer.next_token());
} else if (maybe_token.error().type == errors::End_Of_File) { if (auto maybe_token = std::get_if<Token>(&token_or_eof); maybe_token) {
break; parser.tokens.emplace_back(std::move(*maybe_token));
} else { } else {
return std::move(maybe_token).error(); // We encountered end of file so no more tokens, break the loop
break;
}
} }
auto const result = parser.parse_sequence(); auto const result = parser.parse_sequence();
@ -61,7 +63,14 @@ Result<Ast> Parser::parse_expression()
Result<Ast> Parser::parse_variable_declaration() Result<Ast> Parser::parse_variable_declaration()
{ {
if (!expect(Token::Type::Keyword, "var")) { if (!expect(Token::Type::Keyword, "var")) {
return errors::expected_keyword(Try(peek()), "var"); Error error;
errors::Expected_Keyword kw { .keyword = "var" };
if (token_id >= tokens.size()) {
kw.received_type = ""; // TODO Token type
error.location = peek()->location;
}
error.details = std::move(kw);
return error;
} }
auto var = consume(); auto var = consume();
@ -98,7 +107,10 @@ Result<Ast> Parser::parse_atomic_expression()
// but keywords like `var` announce variable declaration which is higher up in expression parsing. // but keywords like `var` announce variable declaration which is higher up in expression parsing.
// So we need to explicitly allow only keywords that are also literals // So we need to explicitly allow only keywords that are also literals
if (std::find(Literal_Keywords.begin(), Literal_Keywords.end(), peek()->source) == Literal_Keywords.end()) { if (std::find(Literal_Keywords.begin(), Literal_Keywords.end(), peek()->source) == Literal_Keywords.end()) {
return errors::unexpected_token(*peek()); return Error {
.details = errors::Unexpected_Keyword { .keyword = peek()->source },
.location = peek()->location
};
} }
[[fallthrough]]; [[fallthrough]];
case Token::Type::Chord: case Token::Type::Chord:
@ -133,7 +145,9 @@ Result<Ast> Parser::parse_atomic_expression()
} }
return parse_sequence().and_then([&](Ast &&ast) -> Result<Ast> { return parse_sequence().and_then([&](Ast &&ast) -> Result<Ast> {
Try(ensure(Token::Type::Close_Block)); if (not expect(Token::Type::Close_Block)) {
unimplemented("Error handling of this code is not implemented yet");
}
consume(); consume();
if (is_lambda) { if (is_lambda) {
return Ast::lambda(opening.location, std::move(ast), std::move(parameters)); return Ast::lambda(opening.location, std::move(ast), std::move(parameters));
@ -146,21 +160,38 @@ Result<Ast> Parser::parse_atomic_expression()
case Token::Type::Open_Paren: case Token::Type::Open_Paren:
consume(); consume();
return parse_expression().and_then([&](Ast ast) -> Result<Ast> { return parse_expression().and_then([&](Ast ast) -> Result<Ast> {
Try(ensure(Token::Type::Close_Paren)); if (not expect(Token::Type::Close_Paren)) {
unimplemented("Error handling of this code is not implemented yet");
}
consume(); consume();
return ast; return ast;
}); });
default: default:
return peek().and_then([](auto const& token) -> Result<Ast> { return Error {
return tl::unexpected(errors::unexpected_token(token)); .details = errors::internal::Unexpected_Token {
}); .type = "", // TODO fill type
.source = peek()->source,
.when = "atomic expression parsing"
},
.location = peek()->location
};
} }
} }
Result<Ast> Parser::parse_identifier_with_trailing_separators() Result<Ast> Parser::parse_identifier_with_trailing_separators()
{ {
Try(ensure(Token::Type::Symbol)); if (not expect(Token::Type::Symbol)) {
// TODO Specific error message
return Error {
.details = errors::internal::Unexpected_Token {
.type = "", // TODO fill type
.source = peek()->source,
.when = "identifier parsing"
},
.location = peek()->location
};
}
auto lit = Ast::literal(consume()); auto lit = Ast::literal(consume());
while (expect(Token::Type::Expression_Separator)) { consume(); } while (expect(Token::Type::Expression_Separator)) { consume(); }
return lit; return lit;
@ -168,7 +199,17 @@ Result<Ast> Parser::parse_identifier_with_trailing_separators()
Result<Ast> Parser::parse_identifier() Result<Ast> Parser::parse_identifier()
{ {
Try(ensure(Token::Type::Symbol)); if (not expect(Token::Type::Symbol)) {
// TODO Specific error message
return Error {
.details = errors::internal::Unexpected_Token {
.type = "", // TODO fill type
.source = peek()->source,
.when = "identifier parsing"
},
.location = peek()->location
};
}
return Ast::literal(consume()); return Ast::literal(consume());
} }
@ -187,7 +228,7 @@ static Result<std::vector<Ast>> parse_many(
while ((expr = (p.*parser)()).has_value()) { while ((expr = (p.*parser)()).has_value()) {
trees.push_back(std::move(expr).value()); trees.push_back(std::move(expr).value());
if (separator) { if (separator) {
if (auto s = p.ensure(*separator); !s.has_value()) { if (not p.expect(*separator)) {
break; break;
} }
do p.consume(); while (p.expect(*separator)); do p.consume(); while (p.expect(*separator));
@ -204,7 +245,10 @@ static Result<std::vector<Ast>> parse_many(
Result<Token> Parser::peek() const Result<Token> Parser::peek() const
{ {
return token_id >= tokens.size() return token_id >= tokens.size()
? errors::unexpected_end_of_source(tokens.back().location) ? Error {
.details = errors::Unexpected_Empty_Source {},
.location = tokens.back().location
}
: Result<Token>(tokens[token_id]); : Result<Token>(tokens[token_id]);
} }
@ -228,15 +272,6 @@ bool Parser::expect(Token::Type type, std::string_view lexeme) const
return token_id < tokens.size() && tokens[token_id].type == type && tokens[token_id].source == lexeme; return token_id < tokens.size() && tokens[token_id].type == type && tokens[token_id].source == lexeme;
} }
Result<void> Parser::ensure(Token::Type type) const
{
return token_id >= tokens.size()
? errors::unexpected_end_of_source(tokens.back().location)
: tokens[token_id].type != type
? errors::unexpected_token(type, tokens[token_id])
: Result<void>{};
}
// Don't know if it's a good idea to defer parsing of literal values up to value creation, which is current approach. // Don't know if it's a good idea to defer parsing of literal values up to value creation, which is current approach.
// This may create unexpected performance degradation during program evaluation. // This may create unexpected performance degradation during program evaluation.
Ast Ast::literal(Token token) Ast Ast::literal(Token token)

View File

@ -47,6 +47,7 @@ suite intepreter_test = [] {
should("allows only for calling which is callable") = [] { should("allows only for calling which is callable") = [] {
evaluates_to(Value::from(Number(0)), "[i|i] 0"); evaluates_to(Value::from(Number(0)), "[i|i] 0");
#if 0
{ {
Interpreter i; Interpreter i;
{ {
@ -61,6 +62,7 @@ suite intepreter_test = [] {
expect(eq(result.error().type, errors::Not_Callable)); expect(eq(result.error().type, errors::Not_Callable));
} }
} }
#endif
}; };
should("allow for value (in)equality comparisons") = [] { should("allow for value (in)equality comparisons") = [] {

View File

@ -3,6 +3,25 @@
using namespace boost::ut; using namespace boost::ut;
enum class EOF_Status : bool
{
Reject = false,
Accept = true,
};
template<EOF_Status Expect_End_Of_File = EOF_Status::Reject>
static auto handle_end_of_file(
std::variant<Token, End_Of_File> token_or_eof,
reflection::source_location const& sl)
{
if constexpr (Expect_End_Of_File == EOF_Status::Accept) {
expect(std::holds_alternative<End_Of_File>(token_or_eof), sl) << "Expected to encounter end of file";
} else {
expect(std::holds_alternative<Token>(token_or_eof), sl) << "Expected to NOT encounter end of file";
return std::get<Token>(std::move(token_or_eof));
}
}
static void expect_token_type( static void expect_token_type(
Token::Type expected_type, Token::Type expected_type,
std::string source, std::string source,
@ -11,7 +30,8 @@ static void expect_token_type(
Lexer lexer{source}; Lexer lexer{source};
auto result = lexer.next_token(); auto result = lexer.next_token();
expect(result.has_value() >> fatal, sl) << "have not parsed any tokens"; expect(result.has_value() >> fatal, sl) << "have not parsed any tokens";
expect(eq(result->type, expected_type), sl) << "different token type then expected"; auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected_type), sl) << "different token type then expected";
} }
static void expect_token_type_and_value( static void expect_token_type_and_value(
@ -25,8 +45,9 @@ static void expect_token_type_and_value(
expect(result.has_value(), sl) << "have not parsed any tokens"; expect(result.has_value(), sl) << "have not parsed any tokens";
if (result.has_value()) { if (result.has_value()) {
expect(eq(result->type, expected_type), sl) << "different token type then expected"; auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(result->source, expected), sl) << "tokenized source is not equal to original"; expect(eq(token.type, expected_type), sl) << "different token type then expected";
expect(eq(token.source, expected), sl) << "tokenized source is not equal to original";
} }
} }
@ -47,20 +68,20 @@ static void expect_token_type_and_location(
Lexer lexer{source}; Lexer lexer{source};
auto result = lexer.next_token(); auto result = lexer.next_token();
expect(result.has_value() >> fatal, sl) << "have not parsed any tokens"; expect(result.has_value() >> fatal, sl) << "have not parsed any tokens";
expect(eq(result->type, expected_type), sl) << "different token type then expected";
expect(eq(result->location, location), sl) << "tokenized source is at different place then expected"; auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected_type), sl) << "different token type then expected";
expect(eq(token.location, location), sl) << "tokenized source is at different place then expected";
} }
static void expect_empty_file( static void expect_empty_file(
std::string_view source, std::string_view source,
reflection::source_location const& sl = reflection::source_location::current()) reflection::source_location const& sl = reflection::source_location::current())
{ {
Lexer lexer{source}; Lexer lexer{source};
auto result = lexer.next_token(); auto result = lexer.next_token();
expect(!result.has_value(), sl) << "could not produce any tokens from empty file"; expect(result.has_value(), sl) << "Encountered error when expecting end of file";
if (not result.has_value()) { handle_end_of_file<EOF_Status::Accept>(*std::move(result), sl);
expect(result.error() == errors::End_Of_File, sl) << "could not produce any tokens from empty file";
}
} }
template<auto N> template<auto N>
@ -76,14 +97,18 @@ static void expect_token_sequence(
expect(result.has_value(), sl) << "expected token, received nothing"; expect(result.has_value(), sl) << "expected token, received nothing";
if (result.has_value()) { if (result.has_value()) {
expect(eq(result->type, expected.type)) << "different token type then expected"; auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(result->source, expected.source)) << "different token source then expected"; expect(eq(token.type, expected.type)) << "different token type then expected";
expect(eq(result->location, expected.location)) << "different token location then expected"; expect(eq(token.source, expected.source)) << "different token source then expected";
expect(eq(token.location, expected.location)) << "different token location then expected";
} }
} }
auto const result = lexer.next_token(); auto const result = lexer.next_token();
expect(not result.has_value(), sl) << "more tokens then expected"; expect(result.has_value(), sl) << "expected end of file after sequence of tokens";
if (result.has_value()) {
expect(std::holds_alternative<End_Of_File>(*result), sl) << "expected end of file after sequence of tokens";
}
} }
suite lexer_test = [] { suite lexer_test = [] {

View File

@ -169,7 +169,12 @@ Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args)
} }
default: default:
// TODO Fill location // TODO Fill location
return errors::not_callable(std::nullopt, type); return Error {
.details = errors::Not_Callable {
.type = type_name(type)
},
.location = std::nullopt,
};
} }
} }