Better main, refactoring of error system and Result type

This commit is contained in:
Robert Bendun 2022-05-08 00:03:22 +02:00
parent 31262e74a0
commit 35a8345bc5
7 changed files with 184 additions and 71 deletions

View File

@ -2,10 +2,11 @@ MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
Obj=bin/errors.o \
bin/lexer.o \
bin/parser.o \
bin/unicode.o \
Obj=bin/errors.o \
bin/lexer.o \
bin/location.o \
bin/parser.o \
bin/unicode.o \
bin/unicode_tables.o
all: bin/musique bin/unit-tests

View File

@ -21,7 +21,7 @@ enum class Error_Level
Bug
};
static void 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)
{
if (location) {
os << *location;
@ -30,12 +30,34 @@ static void error_heading(std::ostream &os, std::optional<Location> location, Er
}
switch (lvl) {
case Error_Level::Bug: os << ": implementation bug: "; return;
case Error_Level::Error: os << ": error: "; return;
case Error_Level::Notice: os << ": notice: "; return;
case Error_Level::Error: return os << ": error: ";
case Error_Level::Notice: return os << ": notice: ";
// 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: ";
}
}
void assert(bool condition, std::string message, Location loc)
{
if (condition) return;
error_heading(std::cerr, loc, Error_Level::Bug) << message << std::endl;
std::exit(1);
}
[[noreturn]] void unimplemented(Location loc)
{
error_heading(std::cerr, loc, Error_Level::Bug) << "this part was not implemented yet" << std::endl;
std::exit(1);
}
[[noreturn]] void unreachable(Location loc)
{
error_heading(std::cerr, loc, Error_Level::Bug) << "this place should not be reached" << std::endl;
std::exit(1);
}
std::ostream& operator<<(std::ostream& os, Error const& err)
{
error_heading(os, err.location, Error_Level::Error);

View File

@ -221,7 +221,7 @@ auto Lexer::consume_if(auto first, auto second) -> bool
void Lexer::rewind()
{
assert(last_rune_length != 0);
assert(last_rune_length != 0, "cannot rewind to not existing rune");
source = { source.data() - last_rune_length, source.size() + last_rune_length };
token_length -= last_rune_length;
location = prev_location;
@ -262,25 +262,5 @@ std::ostream& operator<<(std::ostream& os, Token::Type type)
case Token::Type::Operator: return os << "OPERATOR";
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
}
assert(false && "exhaustive handling of Token::Type enumeration");
}
Location Location::advance(u32 rune)
{
switch (rune) {
case '\n':
line += 1;
[[fallthrough]];
case '\r':
column = 1;
return *this;
}
column += 1;
return *this;
}
std::ostream& operator<<(std::ostream& os, Location const& location)
{
return os << location.filename << ':' << location.line << ':' << location.column;
unreachable();
}

32
src/location.cc Normal file
View File

@ -0,0 +1,32 @@
#include <musique.hh>
Location Location::advance(u32 rune)
{
switch (rune) {
case '\n':
line += 1;
[[fallthrough]];
case '\r':
column = 1;
return *this;
}
column += 1;
return *this;
}
std::ostream& operator<<(std::ostream& os, Location const& location)
{
return os << location.filename << ':' << location.line << ':' << location.column;
}
#if defined(__cpp_lib_source_location)
Location Location::caller(std::source_location loc = std::source_location::current())
{
return Location { loc.file_name(), loc.line(), loc.column() };
}
#elif (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE))
Location Location::caller(char const* file, usize line)
{
return Location { file, line };
}
#endif

View File

@ -1,7 +1,11 @@
#include <iostream>
#include <musique.hh>
#include <filesystem>
#include <span>
#include <fstream>
namespace fs = std::filesystem;
static std::string_view pop(std::span<char const*> &span)
{
@ -10,11 +14,45 @@ static std::string_view pop(std::span<char const*> &span)
return element;
}
Result<Unit> Main(std::span<char const*> args)
[[noreturn]]
void usage()
{
std::cerr <<
"usage: musique <options> [filename]\n"
" where filename is path to file with Musique code that will be executed\n"
" where options are:\n"
" -c CODE\n"
" --run CODE\n"
" executes given code\n";
std::exit(1);
}
static Result<void> run(std::string_view source, std::string_view filename)
{
Try(Parser::parse(source, filename));
std::cout << "successfully parsed: " << source << " \n";
return {};
}
// We make sure that through life of interpreter source code is allways allocated
std::vector<std::string> eternal_sources;
static Result<void> Main(std::span<char const*> args)
{
if (args.empty()) {
usage();
}
std::vector<std::string_view> files;
while (not args.empty()) {
std::string_view arg = pop(args);
if (!arg.starts_with('-')) {
files.push_back(std::move(arg));
continue;
}
if (arg == "-c" || arg == "--run") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
@ -22,8 +60,7 @@ Result<Unit> Main(std::span<char const*> args)
}
auto const source = pop(args);
Try(Parser::parse(source, "arguments"));
std::cout << "successfully parsed: " << source << " \n";
Try(run(source, "arguments"));
continue;
}
@ -31,12 +68,27 @@ Result<Unit> Main(std::span<char const*> args)
std::exit(1);
}
for (auto const& path : files) {
if (not fs::exists(path)) {
std::cerr << "musique: error: couldn't open file: " << path << std::endl;
std::exit(1);
}
{
std::ifstream source_file{fs::path(path)};
eternal_sources.emplace_back(std::istreambuf_iterator<char>(source_file), std::istreambuf_iterator<char>());
}
Try(run(eternal_sources.back(), path));
}
return {};
}
int main(int argc, char const** argv)
{
auto result = Main(std::span{ argv+1, usize(argc-1) });
auto const args = std::span(argv, argc).subspan(1);
auto const result = Main(args);
if (not result.has_value()) {
std::cerr << result.error() << std::flush;
return 1;

View File

@ -1,6 +1,5 @@
#pragma once
#include <cassert>
#include <concepts>
#include <cstdint>
#include <cstring>
@ -11,6 +10,15 @@
#include <tl/expected.hpp>
#include <variant>
#if defined(__cpp_lib_source_location)
#include <source_location>
#endif
// To make sure, that we don't collide with <cassert> macro
#ifdef assert
#undef assert
#endif
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
@ -24,10 +32,6 @@ using i64 = std::int64_t;
using usize = std::size_t;
using isize = std::ptrdiff_t;
struct Unit {};
#define Fun(Function) ([]<typename ...T>(T&& ...args) { return (Function)(std::forward<T>(args)...); })
// Error handling mechanism inspired by Andrew Kelly approach, that was implemented
// as first class feature in Zig programming language.
namespace errors
@ -45,7 +49,7 @@ namespace errors
struct Location
{
std::string_view filename = "<unnamed>";
usize column = 1, line = 1;
usize line = 1, column = 1;
Location advance(u32 rune);
@ -58,10 +62,27 @@ struct Location
loc.column = column;
return loc;
}
// Used to describe location of function call in interpreter (internal use only)
#if defined(__cpp_lib_source_location)
static Location caller(std::source_location loc = std::source_location::current());
#elif (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE))
static Location caller(char const* file = __builtin_FILE(), usize line = __builtin_LINE());
#else
#error Cannot implement Location::caller function
#endif
};
std::ostream& operator<<(std::ostream& os, Location const& location);
void assert(bool condition, std::string message, Location loc = Location::caller());
// Marks part of code that was not implemented yet
[[noreturn]] void unimplemented(Location loc = Location::caller());
// Marks location that should not be reached
[[noreturn]] void unreachable(Location loc = Location::caller());
struct Error
{
errors::Type type;
@ -72,19 +93,29 @@ struct Error
Error with(Location) &&;
};
std::ostream& operator<<(std::ostream& os, Error const& err);
template<typename T>
struct Result : tl::expected<T, Error>
{
using Storage = tl::expected<T, Error>;
constexpr Result() = default;
constexpr Result(Result const&) = default;
constexpr Result(Result&&) = default;
constexpr Result& operator=(Result const&) = default;
constexpr Result& operator=(Result&&) = default;
constexpr Result() = default;
template<typename ...Args> requires (not std::is_void_v<T>) && std::is_constructible_v<T, Args...>
constexpr Result(Args&& ...args)
: Storage( T{ std::forward<Args>(args)... } )
{
}
template<typename Arg> requires std::is_constructible_v<Storage, Arg>
constexpr Result(Arg &&arg)
: Storage(std::forward<Arg>(arg))
{
}
constexpr Result(errors::Type error)
: Storage(tl::unexpected(Error { error }))
: Storage(tl::unexpect, Error { error } )
{
}
@ -93,36 +124,34 @@ struct Result : tl::expected<T, Error>
{
}
inline Result(tl::unexpected<Error> error)
: Storage(std::move(error))
{
}
template<typename ...Args>
requires std::is_constructible_v<T, Args...>
constexpr Result(Args&& ...args)
: Storage( T{ std::forward<Args>(args)... } )
// Internal function used for definition of Try macro
inline auto value() &&
{
if constexpr (not std::is_void_v<T>) {
return Storage::value();
}
}
};
std::ostream& operator<<(std::ostream& os, Error const& err);
// NOTE This implementation requires C++ language extension: statement expressions
// It's supported by GCC, other compilers i don't know
#define Try(Value) ({ \
auto try_value = (Value); \
if (not try_value.has_value()) return tl::unexpected(try_value.error()); \
*std::move(try_value); \
// It's supported by GCC and Clang, other compilers i don't know
//
// Inspired by SerenityOS TRY macro
#define Try(Value) \
({ \
auto try_value = (Value); \
if (not try_value.has_value()) [[unlikely]] \
return tl::unexpected(try_value.error()); \
std::move(try_value).value(); \
})
namespace unicode
{
inline namespace special_runes
{
constexpr u32 Rune_Error = 0xfffd;
constexpr u32 Rune_Self = 0x80;
constexpr u32 Max_Bytes = 4;
[[maybe_unused]] constexpr u32 Rune_Error = 0xfffd;
[[maybe_unused]] constexpr u32 Rune_Self = 0x80;
[[maybe_unused]] constexpr u32 Max_Bytes = 4;
}
// is_digit returns true if `digit` is ASCII digit
@ -257,9 +286,6 @@ struct Ast
Token token;
};
template<typename Expected, typename ...T>
concept Var_Args = (std::is_same_v<Expected, T> && ...) && (sizeof...(T) >= 1);
struct Parser
{
std::vector<Token> tokens;
@ -280,7 +306,7 @@ struct Parser
// Ensures that current token has one of types given.
// Otherwise returns error
Result<Unit> ensure(Token::Type type) const;
Result<void> ensure(Token::Type type) const;
};
namespace errors

View File

@ -49,13 +49,13 @@ bool Parser::expect(Token::Type type) const
return token_id < tokens.size() && tokens[token_id].type == type;
}
Result<Unit> Parser::ensure(Token::Type type) const
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<Unit>{};
: Result<void>{};
}
Ast Ast::literal(Token token)