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
for program_file in file_paths:
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]
if hasattr(tc, "flags"):

View File

@ -1,6 +1,6 @@
{
"returncode": 0,
"stdout": "4\n30\n42\nnil\n",
"stdout": "4\n30\n42\n",
"stderr": "",
"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,
"stdout": "100\n200\n120\nnil\n",
"stdout": "100\n200\n120\n",
"stderr": "",
"flags": []
}

View File

@ -1,6 +1,6 @@
{
"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": "",
"flags": []
}

View File

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

View File

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

View File

@ -3,9 +3,20 @@
#include <iostream>
#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) &&
@ -17,183 +28,138 @@ Error Error::with(Location loc) &&
enum class Error_Level
{
Error,
Notice,
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) {
case Error_Level::Error: return os << ": error: ";
case Error_Level::Notice: return os << ": notice: ";
case Error_Level::Error:
os << "ERROR " << short_description << ' ';
break;
// This branch should be reached if we have Error_Level::Bug
// 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)
{
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);
}
[[noreturn]] void unimplemented(std::string_view message, Location loc)
{
if (message.empty()) {
error_heading(std::cerr, loc, Error_Level::Bug) << "this part was not implemented yet" << std::endl;
} else {
error_heading(std::cerr, loc, Error_Level::Bug) << message << std::endl;
error_heading(std::cerr, loc, Error_Level::Bug, "This part of interpreter was not implemented yet");
if (not message.empty()) {
std::cerr << message << std::endl;
}
encourage_contact(std::cerr);
std::exit(1);
}
[[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::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) {
case errors::End_Of_File:
return os << "end of file\n";
error_heading(os, err.location, Error_Level::Error, short_description);
case errors::Unrecognized_Character:
return err.message.empty() ? os << "unrecognized character\n" : os << err.message;
visit(Overloaded {
[&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:
case errors::Function_Not_Defined:
case errors::Not_Callable:
case errors::Unexpected_Token_Type:
case errors::Unresolved_Operator:
return os << err.message << '\n';
case errors::Unexpected_Empty_Source:
return os << "unexpected end of input\n";
case errors::Failed_Numeric_Parsing:
return err.error_code == std::errc::result_out_of_range
? os << "number " << err.message << " cannot be represented with " << (sizeof(Number::num)*8) << " bit number\n"
: os << "couldn't parse number " << err.message << '\n';
[&os](errors::Failed_Numeric_Parsing const& err) {
constexpr auto Max = std::numeric_limits<decltype(Number::num)>::max();
constexpr auto Min = std::numeric_limits<decltype(Number::num)>::min();
os << "I tried to parse numeric literal, but I failed.";
if (err.reason == std::errc::result_out_of_range) {
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";
}
},
return os << "unrecognized error type\n";
}
[&os](errors::internal::Unexpected_Token const& ut) {
os << "I encountered unexpected token during " << ut.when << '\n';
os << " Token type: " << ut.type << '\n';
os << " Token source: " << ut.source << '\n';
static std::string format(auto const& ...args)
{
std::stringstream ss;
(void) (ss << ... << args);
return ss.str();
}
os << "\nThis error is considered an internal one. It should not be displayed to the end user.\n";
encourage_contact(os);
},
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}, "')");
[&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);
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;
return os;
}
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";
for (auto const& token : tokens) {
error_heading(std::cerr, token.location, Error_Level::Notice);
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));
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) {

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();
start();
if (peek() == 0) {
return errors::End_Of_File;
return End_Of_File{};
}
switch (peek()) {
@ -135,7 +135,10 @@ auto Lexer::next_token() -> Result<Token>
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

View File

@ -45,22 +45,86 @@ using isize = std::ptrdiff_t;
/// as first class feature in Zig programming language.
namespace errors
{
enum Type
struct Unrecognized_Character
{
End_Of_File,
Unrecognized_Character,
Unexpected_Token_Type,
Unexpected_Empty_Source,
Failed_Numeric_Parsing,
Function_Not_Defined,
Unresolved_Operator,
Expected_Keyword,
Not_Callable
u32 invalid_character;
};
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.
/// It's used both to represent position in source files provided
// to interpreter and internal interpreter usage.
@ -113,12 +177,9 @@ void assert(bool condition, std::string message, Location loc = Location::caller
struct Error
{
errors::Type type;
errors::Details details;
std::optional<Location> location = std::nullopt;
std::string message{};
std::errc error_code{};
bool operator==(errors::Type);
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)
: Storage(tl::unexpected(std::move(error)))
{
@ -300,6 +356,9 @@ std::ostream& operator<<(std::ostream& os, Token const& tok);
/// Token type debug printing
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
///
/// It allows for creating sequence of tokens by using next_token() method.
@ -332,8 +391,8 @@ struct Lexer
/// Used only for rewinding
Location prev_location{};
/// Try to tokenize next token
auto next_token() -> Result<Token>;
/// Try to tokenize next token.
auto next_token() -> Result<std::variant<Token, End_Of_File>>;
/// 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
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
@ -787,21 +843,6 @@ struct Interpreter
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]]
void all_tokens_were_not_parsed(std::span<Token>);
}

View File

@ -165,7 +165,10 @@ Result<Number> Number::from(Token token)
num_end = begin;
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;
@ -177,7 +180,10 @@ parse_fractional:
if (ec != std::errc{}) {
if (parsed_numerator && ec == std::errc::invalid_argument && token.source != ".")
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) };
}

View File

@ -27,12 +27,14 @@ Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
lexer.location.filename = filename;
Parser parser;
for (;;) if (auto maybe_token = lexer.next_token(); maybe_token.has_value()) {
parser.tokens.emplace_back(*std::move(maybe_token));
} else if (maybe_token.error().type == errors::End_Of_File) {
break;
for (;;) {
auto token_or_eof = Try(lexer.next_token());
if (auto maybe_token = std::get_if<Token>(&token_or_eof); maybe_token) {
parser.tokens.emplace_back(std::move(*maybe_token));
} 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();
@ -61,7 +63,14 @@ Result<Ast> Parser::parse_expression()
Result<Ast> Parser::parse_variable_declaration()
{
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();
@ -98,7 +107,10 @@ Result<Ast> Parser::parse_atomic_expression()
// 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
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]];
case Token::Type::Chord:
@ -133,7 +145,9 @@ Result<Ast> Parser::parse_atomic_expression()
}
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();
if (is_lambda) {
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:
consume();
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();
return ast;
});
default:
return peek().and_then([](auto const& token) -> Result<Ast> {
return tl::unexpected(errors::unexpected_token(token));
});
return Error {
.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()
{
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());
while (expect(Token::Type::Expression_Separator)) { consume(); }
return lit;
@ -168,7 +199,17 @@ Result<Ast> Parser::parse_identifier_with_trailing_separators()
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());
}
@ -187,7 +228,7 @@ static Result<std::vector<Ast>> parse_many(
while ((expr = (p.*parser)()).has_value()) {
trees.push_back(std::move(expr).value());
if (separator) {
if (auto s = p.ensure(*separator); !s.has_value()) {
if (not p.expect(*separator)) {
break;
}
do p.consume(); while (p.expect(*separator));
@ -204,7 +245,10 @@ static Result<std::vector<Ast>> parse_many(
Result<Token> Parser::peek() const
{
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]);
}
@ -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;
}
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.
// This may create unexpected performance degradation during program evaluation.
Ast Ast::literal(Token token)

View File

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

View File

@ -3,6 +3,25 @@
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(
Token::Type expected_type,
std::string source,
@ -11,7 +30,8 @@ static void expect_token_type(
Lexer lexer{source};
auto result = lexer.next_token();
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(
@ -25,8 +45,9 @@ static void expect_token_type_and_value(
expect(result.has_value(), sl) << "have not parsed any tokens";
if (result.has_value()) {
expect(eq(result->type, expected_type), sl) << "different token type then expected";
expect(eq(result->source, expected), sl) << "tokenized source is not equal to original";
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.source, expected), sl) << "tokenized source is not equal to original";
}
}
@ -47,8 +68,10 @@ static void expect_token_type_and_location(
Lexer lexer{source};
auto result = lexer.next_token();
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(
@ -57,10 +80,8 @@ static void expect_empty_file(
{
Lexer lexer{source};
auto result = lexer.next_token();
expect(!result.has_value(), sl) << "could not produce any tokens from empty file";
if (not result.has_value()) {
expect(result.error() == errors::End_Of_File, sl) << "could not produce any tokens from empty file";
}
expect(result.has_value(), sl) << "Encountered error when expecting end of file";
handle_end_of_file<EOF_Status::Accept>(*std::move(result), sl);
}
template<auto N>
@ -76,14 +97,18 @@ static void expect_token_sequence(
expect(result.has_value(), sl) << "expected token, received nothing";
if (result.has_value()) {
expect(eq(result->type, expected.type)) << "different token type then expected";
expect(eq(result->source, expected.source)) << "different token source then expected";
expect(eq(result->location, expected.location)) << "different token location then expected";
auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected.type)) << "different token type 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();
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 = [] {

View File

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