Better main, refactoring of error system and Result type
This commit is contained in:
parent
31262e74a0
commit
35a8345bc5
9
Makefile
9
Makefile
@ -2,10 +2,11 @@ MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
|||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch
|
||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
||||||
|
|
||||||
Obj=bin/errors.o \
|
Obj=bin/errors.o \
|
||||||
bin/lexer.o \
|
bin/lexer.o \
|
||||||
bin/parser.o \
|
bin/location.o \
|
||||||
bin/unicode.o \
|
bin/parser.o \
|
||||||
|
bin/unicode.o \
|
||||||
bin/unicode_tables.o
|
bin/unicode_tables.o
|
||||||
|
|
||||||
all: bin/musique bin/unit-tests
|
all: bin/musique bin/unit-tests
|
||||||
|
@ -21,7 +21,7 @@ enum class Error_Level
|
|||||||
Bug
|
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) {
|
if (location) {
|
||||||
os << *location;
|
os << *location;
|
||||||
@ -30,12 +30,34 @@ static void error_heading(std::ostream &os, std::optional<Location> location, Er
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (lvl) {
|
switch (lvl) {
|
||||||
case Error_Level::Bug: os << ": implementation bug: "; return;
|
case Error_Level::Error: return os << ": error: ";
|
||||||
case Error_Level::Error: os << ": error: "; return;
|
case Error_Level::Notice: return os << ": notice: ";
|
||||||
case Error_Level::Notice: os << ": notice: "; return;
|
|
||||||
|
// 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)
|
std::ostream& operator<<(std::ostream& os, Error const& err)
|
||||||
{
|
{
|
||||||
error_heading(os, err.location, Error_Level::Error);
|
error_heading(os, err.location, Error_Level::Error);
|
||||||
|
24
src/lexer.cc
24
src/lexer.cc
@ -221,7 +221,7 @@ auto Lexer::consume_if(auto first, auto second) -> bool
|
|||||||
|
|
||||||
void Lexer::rewind()
|
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 };
|
source = { source.data() - last_rune_length, source.size() + last_rune_length };
|
||||||
token_length -= last_rune_length;
|
token_length -= last_rune_length;
|
||||||
location = prev_location;
|
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::Operator: return os << "OPERATOR";
|
||||||
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
|
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
|
||||||
}
|
}
|
||||||
|
unreachable();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
32
src/location.cc
Normal file
32
src/location.cc
Normal 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
|
60
src/main.cc
60
src/main.cc
@ -1,7 +1,11 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <musique.hh>
|
#include <musique.hh>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
static std::string_view pop(std::span<char const*> &span)
|
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;
|
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()) {
|
while (not args.empty()) {
|
||||||
std::string_view arg = pop(args);
|
std::string_view arg = pop(args);
|
||||||
|
|
||||||
|
if (!arg.starts_with('-')) {
|
||||||
|
files.push_back(std::move(arg));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "-c" || arg == "--run") {
|
if (arg == "-c" || arg == "--run") {
|
||||||
if (args.empty()) {
|
if (args.empty()) {
|
||||||
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
|
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);
|
auto const source = pop(args);
|
||||||
Try(Parser::parse(source, "arguments"));
|
Try(run(source, "arguments"));
|
||||||
std::cout << "successfully parsed: " << source << " \n";
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +68,27 @@ Result<Unit> Main(std::span<char const*> args)
|
|||||||
std::exit(1);
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char const** argv)
|
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()) {
|
if (not result.has_value()) {
|
||||||
std::cerr << result.error() << std::flush;
|
std::cerr << result.error() << std::flush;
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -11,6 +10,15 @@
|
|||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
#include <variant>
|
#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 u8 = std::uint8_t;
|
||||||
using u16 = std::uint16_t;
|
using u16 = std::uint16_t;
|
||||||
using u32 = std::uint32_t;
|
using u32 = std::uint32_t;
|
||||||
@ -24,10 +32,6 @@ using i64 = std::int64_t;
|
|||||||
using usize = std::size_t;
|
using usize = std::size_t;
|
||||||
using isize = std::ptrdiff_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
|
// Error handling mechanism inspired by Andrew Kelly approach, that was implemented
|
||||||
// as first class feature in Zig programming language.
|
// as first class feature in Zig programming language.
|
||||||
namespace errors
|
namespace errors
|
||||||
@ -45,7 +49,7 @@ namespace errors
|
|||||||
struct Location
|
struct Location
|
||||||
{
|
{
|
||||||
std::string_view filename = "<unnamed>";
|
std::string_view filename = "<unnamed>";
|
||||||
usize column = 1, line = 1;
|
usize line = 1, column = 1;
|
||||||
|
|
||||||
Location advance(u32 rune);
|
Location advance(u32 rune);
|
||||||
|
|
||||||
@ -58,10 +62,27 @@ struct Location
|
|||||||
loc.column = column;
|
loc.column = column;
|
||||||
return loc;
|
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);
|
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
|
struct Error
|
||||||
{
|
{
|
||||||
errors::Type type;
|
errors::Type type;
|
||||||
@ -72,19 +93,29 @@ struct Error
|
|||||||
Error with(Location) &&;
|
Error with(Location) &&;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Error const& err);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct Result : tl::expected<T, Error>
|
struct Result : tl::expected<T, Error>
|
||||||
{
|
{
|
||||||
using Storage = tl::expected<T, Error>;
|
using Storage = tl::expected<T, Error>;
|
||||||
|
|
||||||
constexpr Result() = default;
|
constexpr Result() = default;
|
||||||
constexpr Result(Result const&) = default;
|
|
||||||
constexpr Result(Result&&) = default;
|
template<typename ...Args> requires (not std::is_void_v<T>) && std::is_constructible_v<T, Args...>
|
||||||
constexpr Result& operator=(Result const&) = default;
|
constexpr Result(Args&& ...args)
|
||||||
constexpr Result& operator=(Result&&) = default;
|
: 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)
|
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)
|
// Internal function used for definition of Try macro
|
||||||
: Storage(std::move(error))
|
inline auto value() &&
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename ...Args>
|
|
||||||
requires std::is_constructible_v<T, Args...>
|
|
||||||
constexpr Result(Args&& ...args)
|
|
||||||
: Storage( T{ std::forward<Args>(args)... } )
|
|
||||||
{
|
{
|
||||||
|
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
|
// NOTE This implementation requires C++ language extension: statement expressions
|
||||||
// It's supported by GCC, other compilers i don't know
|
// It's supported by GCC and Clang, other compilers i don't know
|
||||||
#define Try(Value) ({ \
|
//
|
||||||
auto try_value = (Value); \
|
// Inspired by SerenityOS TRY macro
|
||||||
if (not try_value.has_value()) return tl::unexpected(try_value.error()); \
|
#define Try(Value) \
|
||||||
*std::move(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
|
namespace unicode
|
||||||
{
|
{
|
||||||
inline namespace special_runes
|
inline namespace special_runes
|
||||||
{
|
{
|
||||||
constexpr u32 Rune_Error = 0xfffd;
|
[[maybe_unused]] constexpr u32 Rune_Error = 0xfffd;
|
||||||
constexpr u32 Rune_Self = 0x80;
|
[[maybe_unused]] constexpr u32 Rune_Self = 0x80;
|
||||||
constexpr u32 Max_Bytes = 4;
|
[[maybe_unused]] constexpr u32 Max_Bytes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is_digit returns true if `digit` is ASCII digit
|
// is_digit returns true if `digit` is ASCII digit
|
||||||
@ -257,9 +286,6 @@ struct Ast
|
|||||||
Token token;
|
Token token;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Expected, typename ...T>
|
|
||||||
concept Var_Args = (std::is_same_v<Expected, T> && ...) && (sizeof...(T) >= 1);
|
|
||||||
|
|
||||||
struct Parser
|
struct Parser
|
||||||
{
|
{
|
||||||
std::vector<Token> tokens;
|
std::vector<Token> tokens;
|
||||||
@ -280,7 +306,7 @@ struct Parser
|
|||||||
|
|
||||||
// Ensures that current token has one of types given.
|
// Ensures that current token has one of types given.
|
||||||
// Otherwise returns error
|
// Otherwise returns error
|
||||||
Result<Unit> ensure(Token::Type type) const;
|
Result<void> ensure(Token::Type type) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
|
@ -49,13 +49,13 @@ bool Parser::expect(Token::Type type) const
|
|||||||
return token_id < tokens.size() && tokens[token_id].type == type;
|
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()
|
return token_id >= tokens.size()
|
||||||
? errors::unexpected_end_of_source(tokens.back().location)
|
? errors::unexpected_end_of_source(tokens.back().location)
|
||||||
: tokens[token_id].type != type
|
: tokens[token_id].type != type
|
||||||
? errors::unexpected_token(type, tokens[token_id])
|
? errors::unexpected_token(type, tokens[token_id])
|
||||||
: Result<Unit>{};
|
: Result<void>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ast Ast::literal(Token token)
|
Ast Ast::literal(Token token)
|
||||||
|
Loading…
Reference in New Issue
Block a user