diff --git a/TODO b/TODO index 343a43b..79adde2 100644 --- a/TODO +++ b/TODO @@ -7,14 +7,13 @@ - Move constants to the symbol table, so we can check at parse time for duplicates. - Don't pass raw pointers to the visitor methods. - While loop. -- Comparision operators. +- Type checking. - Procedures. - Calculate additional stack space needed for subexpressions in the allocator visitor and not in the backend. - Support immediates greater than 12 bits. - It seems instructions are correctly encoded only if the compiler is running on a little endian architecture. -- Print filename in the error message. - Merge declaration and definition nodes. # Shell @@ -33,4 +32,3 @@ ## Appearance - Add a bar with additional information under the prompt (edit_bar), like the hostname. -- Show current time in the prompt. diff --git a/cli/cl.cpp b/cli/cl.cpp index 61dfeed..2a9f31a 100644 --- a/cli/cl.cpp +++ b/cli/cl.cpp @@ -3,55 +3,70 @@ #include "elna/source/semantic.hpp" #include #include -#include #include namespace elna::cli { - char *readSource(const char *source) + std::string read_source(const char *source) { - const std::size_t bufferSize = 255; + constexpr std::size_t buffer_size = 4096; - std::ifstream input_stream{ source }; - std::stringstream buffer; - buffer << input_stream.rdbuf(); - input_stream.close(); - std::string contents = buffer.str(); - char *result = reinterpret_cast(malloc(contents.size() + 1)); - std::copy(std::cbegin(contents), std::cend(contents), result); - result[contents.size()] = '\0'; + std::ifstream input_stream{ source, std::ios::binary | std::ios::in }; + std::string output; + if (input_stream.fail()) + { + throw std::ios_base::failure("File does not exist"); + } + while (true) + { + const std::size_t old_size = output.size(); + output.resize(old_size + buffer_size); + input_stream.read(&output[old_size], buffer_size); - return result; + if (input_stream.eof()) + { + output.resize(old_size + input_stream.gcount()); + break; + } + else if (input_stream.fail()) + { + throw std::ios_base::failure("Unable to complete reading the source file"); + } + } + return output; + } + + void print_error(const std::unique_ptr& compile_error) + { + std::cerr << compile_error->path().string() << ":" + << compile_error->line() << ':' << compile_error->column() + << ": " << compile_error->what() << std::endl; } int compile(const std::filesystem::path& in_file, const std::filesystem::path& out_file) { - auto sourceText = readSource(in_file.c_str()); - if (sourceText == nullptr) + std::string source_text; + try + { + source_text = read_source(in_file.c_str()); + } + catch (std::ios_base::failure&) { return 3; } size_t tokensCount{ 0 }; - auto lex_result = source::lex(sourceText); - free(sourceText); + auto lex_result = source::lex(source_text, in_file); + if (lex_result.has_errors()) { - for (const auto& compile_error : lex_result.errors()) - { - std::cerr << compile_error->line() << ':' << compile_error->column() - << ": " << compile_error->what() << std::endl; - } + print_errors(lex_result.errors().cbegin(), lex_result.errors().cend()); return 1; } source::parser parser{ std::move(lex_result.success()) }; auto ast = parser.parse(); if (ast == nullptr) { - for (const auto& compile_error : parser.errors()) - { - std::cerr << compile_error->line() << ':' << compile_error->column() - << ": " << compile_error->what() << std::endl; - } + print_errors(parser.errors().cbegin(), parser.errors().cend()); return 2; } auto global_scope = std::make_shared(); diff --git a/include/elna/cli/cl.hpp b/include/elna/cli/cl.hpp index 2d12d39..e98672c 100644 --- a/include/elna/cli/cl.hpp +++ b/include/elna/cli/cl.hpp @@ -1,9 +1,46 @@ #pragma once +#include #include +#include "elna/source/result.hpp" namespace elna::cli { - char *readSource(const char *source); + /** + * Reads an input file and returns its contents. + * + * \param source Input file. + * + * \return File contents. + */ + std::string read_source(const char *source); + + /** + * Formats and prints the given error. + * + * \param compile_error The error to print. + */ + void print_error(const std::unique_ptr& compile_error); + + /** + * Prints the given errors to the standard output. + * + * \param begin Pointer to the first error. + * \param end Pointer pass the last error. + */ + template + void print_errors(I begin, I end) + { + std::for_each(begin, end, &print_error); + } + + /** + * Compiles \a in_file and writes the generated code into \a out_file. + * + * \param in_file Input file. + * \param out_file Output file. + * + * \return Exit status. + */ int compile(const std::filesystem::path& in_file, const std::filesystem::path& out_file); } diff --git a/include/elna/source/lexer.hpp b/include/elna/source/lexer.hpp index 0c772e9..c6e7fd0 100644 --- a/include/elna/source/lexer.hpp +++ b/include/elna/source/lexer.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -123,7 +124,13 @@ namespace elna::source std::string character; public: - unexpected_character(const std::string& character, const source::position position); + /** + * \param character Unexpected character. + * \param path Source file name. + * \param position Unexpected token position. + */ + unexpected_character(const std::string& character, const std::filesystem::path& path, + const source::position position); std::string what() const override; }; @@ -133,14 +140,18 @@ namespace elna::source token m_token; public: - explicit unexpected_token(const token& token); + /** + * \param token Unexpected token. + * \param path Source file name. + */ + unexpected_token(const token& token, const std::filesystem::path& path); std::string what() const override; }; struct lexer { - lexer(std::vector&& tokens, const position last_position); + lexer(std::vector&& tokens, const position last_position, const std::filesystem::path& path); lexer(const lexer&) = delete; lexer(lexer&&) = default; @@ -217,6 +228,7 @@ namespace elna::source std::vector tokens; std::vector::const_iterator iterator; std::list> m_errors; + std::filesystem::path source_file; token eof; }; @@ -224,7 +236,8 @@ namespace elna::source * Splits the source text into tokens. * * \param buffer Source text. + * \param path Source file location. * \return Tokens or error. */ - elna::source::result lex(const std::string& buffer); + elna::source::result lex(const std::string& buffer, const std::filesystem::path& path); } diff --git a/include/elna/source/result.hpp b/include/elna/source/result.hpp index ef9b34c..0972679 100644 --- a/include/elna/source/result.hpp +++ b/include/elna/source/result.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -30,12 +31,16 @@ namespace elna::source class error { position m_position; + std::filesystem::path m_path; protected: /** + * Constructs an error. + * + * \param path Source file name. * \param position Error position in the source text. */ - error(const position position); + error(const std::filesystem::path& path, const position position); public: /// Error text. @@ -46,6 +51,9 @@ namespace elna::source /// Error column in the source text. std::size_t column() const noexcept; + + /// Source file name. + const std::filesystem::path& path() const noexcept; }; template @@ -106,7 +114,14 @@ namespace elna::source std::string name; public: - name_collision(const std::string& name, const position current, const position previous); + /** + * \param name Symbol name. + * \param path Source file name. + * \param current Current symbol position. + * \param previous Position of the previously defined symbol. + */ + name_collision(const std::string& name, const std::filesystem::path& path, + const position current, const position previous); std::string what() const override; }; diff --git a/source/lexer.cpp b/source/lexer.cpp index bfd5683..279bffe 100644 --- a/source/lexer.cpp +++ b/source/lexer.cpp @@ -267,8 +267,9 @@ namespace elna::source assert(false); } - unexpected_character::unexpected_character(const std::string& character, const source::position position) - : error(position), character(character) + unexpected_character::unexpected_character(const std::string& character, const std::filesystem::path& path, + const source::position position) + : error(path, position), character(character) { } @@ -281,8 +282,8 @@ namespace elna::source return ss.str(); } - unexpected_token::unexpected_token(const token& token) - : error(token.position()), m_token(token) + unexpected_token::unexpected_token(const token& token, const std::filesystem::path& path) + : error(path, token.position()), m_token(token) { } @@ -291,8 +292,9 @@ namespace elna::source return "Unexpected token " + m_token.to_string(); } - lexer::lexer(std::vector&& tokens, const position last_position) - : tokens(std::move(tokens)), iterator(this->tokens.cbegin()), eof(token(token::type::eof, last_position)) + lexer::lexer(std::vector&& tokens, const position last_position, const std::filesystem::path& path) + : tokens(std::move(tokens)), iterator(this->tokens.cbegin()), eof(token(token::type::eof, last_position)), + source_file(path) { } @@ -328,7 +330,7 @@ namespace elna::source void lexer::add_error(const token& expected) { - m_errors.push_back(std::make_unique(expected)); + m_errors.push_back(std::make_unique(expected, this->source_file)); } std::optional> lexer::advance(const token::type token_type) @@ -367,7 +369,7 @@ namespace elna::source return m_errors; } - result lex(const std::string& buffer) + result lex(const std::string& buffer, const std::filesystem::path& path) { std::vector tokens; auto [iterator, text_end] = text_iterators(buffer); @@ -492,10 +494,10 @@ namespace elna::source } else { - return result(unexpected_character{ std::string{ *iterator }, iterator.position() }); + return result(unexpected_character{ std::string{ *iterator }, path, iterator.position() }); } ++iterator; } - return result(std::in_place, std::move(tokens), iterator.position()); + return result(std::in_place, std::move(tokens), iterator.position(), path); } } diff --git a/source/result.cpp b/source/result.cpp index 471e4ff..182e156 100644 --- a/source/result.cpp +++ b/source/result.cpp @@ -2,8 +2,8 @@ namespace elna::source { - error::error(const position position) - : m_position(position) + error::error(const std::filesystem::path& path, const position position) + : m_position(position), m_path(path) { } @@ -17,8 +17,14 @@ namespace elna::source return this->m_position.column; } - name_collision::name_collision(const std::string& name, const position current, const position previous) - : error(current), name(name), previous(previous) + const std::filesystem::path& error::path() const noexcept + { + return this->m_path; + } + + name_collision::name_collision(const std::string& name, const std::filesystem::path& path, + const position current, const position previous) + : error(path, current), name(name), previous(previous) { } diff --git a/tests/failures/single_word_error.txt b/tests/failures/single_word_error.txt index 8cdf363..5cd16c0 100644 --- a/tests/failures/single_word_error.txt +++ b/tests/failures/single_word_error.txt @@ -1 +1 @@ -1:1: Unexpected token «identifier» +tests/single_word_error.eln:1:1: Unexpected token «identifier»