diff --git a/Makefile b/Makefile index fb28775..4d0da8d 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/errors.cc b/src/errors.cc index 6efe905..68b17d8 100644 --- a/src/errors.cc +++ b/src/errors.cc @@ -21,7 +21,7 @@ enum class Error_Level Bug }; -static void error_heading(std::ostream &os, std::optional location, Error_Level lvl) +static std::ostream& error_heading(std::ostream &os, std::optional location, Error_Level lvl) { if (location) { os << *location; @@ -30,12 +30,34 @@ static void error_heading(std::ostream &os, std::optional 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); diff --git a/src/lexer.cc b/src/lexer.cc index 60bd4fe..4af080b 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -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(); } diff --git a/src/location.cc b/src/location.cc new file mode 100644 index 0000000..03292a9 --- /dev/null +++ b/src/location.cc @@ -0,0 +1,32 @@ +#include + +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 diff --git a/src/main.cc b/src/main.cc index 5a7b759..1d9efdf 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,7 +1,11 @@ #include #include +#include #include +#include + +namespace fs = std::filesystem; static std::string_view pop(std::span &span) { @@ -10,11 +14,45 @@ static std::string_view pop(std::span &span) return element; } -Result Main(std::span args) +[[noreturn]] +void usage() { + std::cerr << + "usage: musique [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 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 eternal_sources; + +static Result Main(std::span args) +{ + if (args.empty()) { + usage(); + } + + std::vector 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 Main(std::span 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 Main(std::span 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(source_file), std::istreambuf_iterator()); + } + + 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; diff --git a/src/musique.hh b/src/musique.hh index 855f508..3302df8 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -11,6 +10,15 @@ #include #include +#if defined(__cpp_lib_source_location) +#include +#endif + +// To make sure, that we don't collide with 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) ([](T&& ...args) { return (Function)(std::forward(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 = ""; - 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 struct Result : tl::expected { using Storage = tl::expected; - 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 requires (not std::is_void_v) && std::is_constructible_v + constexpr Result(Args&& ...args) + : Storage( T{ std::forward(args)... } ) + { + } + + template requires std::is_constructible_v + constexpr Result(Arg &&arg) + : Storage(std::forward(arg)) + { + } constexpr Result(errors::Type error) - : Storage(tl::unexpected(Error { error })) + : Storage(tl::unexpect, Error { error } ) { } @@ -93,36 +124,34 @@ struct Result : tl::expected { } - inline Result(tl::unexpected error) - : Storage(std::move(error)) - { - } - - template - requires std::is_constructible_v - constexpr Result(Args&& ...args) - : Storage( T{ std::forward(args)... } ) + // Internal function used for definition of Try macro + inline auto value() && { + if constexpr (not std::is_void_v) { + 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 -concept Var_Args = (std::is_same_v && ...) && (sizeof...(T) >= 1); - struct Parser { std::vector tokens; @@ -280,7 +306,7 @@ struct Parser // Ensures that current token has one of types given. // Otherwise returns error - Result ensure(Token::Type type) const; + Result ensure(Token::Type type) const; }; namespace errors diff --git a/src/parser.cc b/src/parser.cc index 917cd30..60e3e62 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -49,13 +49,13 @@ bool Parser::expect(Token::Type type) const return token_id < tokens.size() && tokens[token_id].type == type; } -Result Parser::ensure(Token::Type type) const +Result 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{}; + : Result{}; } Ast Ast::literal(Token token)