From 8240443cd1614451091d03a579dfc12c3c2533a6 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Thu, 14 Mar 2024 08:52:45 +0100 Subject: [PATCH] Assign variables --- CMakeLists.txt | 2 +- TODO | 10 +- backend/riscv.cpp | 43 ++++-- cli/cl.cpp | 5 +- include/elna/backend/riscv.hpp | 5 +- include/elna/source/lexer.hpp | 87 ++++++++++- include/elna/source/parser.hpp | 63 +++++--- include/elna/source/result.hpp | 79 +++++++++- include/elna/source/semantic.hpp | 25 ++++ include/elna/source/symboltable.hpp | 73 --------- include/elna/tester.hpp | 23 ++- source/lexer.cpp | 85 ++++++++++- source/parser.cpp | 224 +++++++++++++++++++--------- source/result.cpp | 68 +++++++++ source/semantic.cpp | 38 +++++ source/symboltable.cpp | 104 ------------- tests/declare_variable.eln | 2 +- tests/tester.cpp | 103 ++++++++----- 18 files changed, 700 insertions(+), 339 deletions(-) create mode 100644 include/elna/source/semantic.hpp delete mode 100644 include/elna/source/symboltable.hpp create mode 100644 source/semantic.cpp delete mode 100644 source/symboltable.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a494b5..1fadc02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ add_executable(elna cli/main.cpp source/lexer.cpp include/elna/source/lexer.hpp source/parser.cpp include/elna/source/parser.hpp source/result.cpp include/elna/source/result.hpp - source/symboltable.cpp include/elna/source/symboltable.hpp + source/semantic.cpp include/elna/source/semantic.hpp backend/riscv.cpp include/elna/backend/riscv.hpp backend/target.cpp include/elna/backend/target.hpp cli/cl.cpp include/elna/cli/cl.hpp diff --git a/TODO b/TODO index e768a6e..d5b9dcc 100644 --- a/TODO +++ b/TODO @@ -5,13 +5,15 @@ - Parser should be able to collect errors. - Provide position information on parse tree nodes. - Move constants to the symbol table, so we can check at parse time for duplicates. -- Allow defining variables. - Don't pass raw pointers to the visitor methods. -- Make error abstract and derive unexpected_token in the lex module from it. -- Wrap the tokens in a struct with methods for incrementing and lookups. - While loop. - If condition. -- Grouping multiple statements with begin and end (compound_statement). +- Introduce program node which contains global state and functions. +- 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. # Shell - Persist the history. diff --git a/backend/riscv.cpp b/backend/riscv.cpp index ca29b83..847655c 100644 --- a/backend/riscv.cpp +++ b/backend/riscv.cpp @@ -63,7 +63,6 @@ namespace elna::riscv void visitor::visit(source::definition *definition) { - constants[definition->identifier()] = definition->body().number(); } void visitor::visit(source::block *block) @@ -73,14 +72,7 @@ namespace elna::riscv this->instructions.push_back(instruction(base_opcode::store)); this->instructions.push_back(instruction(base_opcode::opImm)); - for (const auto& block_definition : block->definitions()) - { - block_definition->accept(this); - } - for (const auto& block_declaration : block->declarations()) - { - block_declaration->accept(this); - } + table = block->table(); block->body().accept(this); // Set the return value (0). @@ -88,7 +80,9 @@ namespace elna::riscv .r(x_register::a0, funct3_t::_and, x_register::zero, x_register::zero)); // Prologue. - const uint stack_size = static_cast(variable_counter * 4 + 12); + auto main_symbol = + std::dynamic_pointer_cast(table->lookup("main")); + const uint stack_size = static_cast(variable_counter * 4 + 8 + main_symbol->stack_size()); this->instructions[0].i(x_register::sp, funct3_t::addi, x_register::sp, -stack_size); this->instructions[1].s(stack_size - 4, funct3_t::sw, x_register::sp, x_register::s0); @@ -143,18 +137,37 @@ namespace elna::riscv } } - void visitor::visit(source::assignment_statement *statement) + void visitor::visit(source::assign_statement *statement) { + const auto free_register = this->register_in_use ? x_register::a0 : x_register::t0; + auto symbol = table->lookup(statement->lvalue()); + auto variable_symbol = std::dynamic_pointer_cast(symbol); + + statement->rvalue().accept(this); + + this->instructions.push_back(instruction(base_opcode::store) + .s(variable_symbol->offset, funct3_t::sw, x_register::s0, x_register::a0)); } void visitor::visit(source::variable_expression *variable) { const auto free_register = this->register_in_use ? x_register::a0 : x_register::t0; - this->instructions.push_back( - instruction(base_opcode::opImm) // movl $x, %eax; where $x is a number. - .i(free_register, funct3_t::addi, x_register::zero, constants[variable->name()]) - ); + auto symbol = table->lookup(variable->name()); + if (auto constant_symbol = std::dynamic_pointer_cast(symbol)) + { + this->instructions.push_back( + instruction(base_opcode::opImm) // movl $x, %eax; where $x is a number. + .i(free_register, funct3_t::addi, x_register::zero, constant_symbol->value()) + ); + } + else if (auto variable_symbol = std::dynamic_pointer_cast(symbol)) + { + this->instructions.push_back( + instruction(base_opcode::store) + .i(free_register, funct3_t::lw, x_register::s0, variable_symbol->offset) + ); + } } void visitor::visit(source::integer_literal *number) diff --git a/cli/cl.cpp b/cli/cl.cpp index fe1a124..1c8b49c 100644 --- a/cli/cl.cpp +++ b/cli/cl.cpp @@ -1,5 +1,6 @@ #include "elna/cli/cl.hpp" #include "elna/backend/target.hpp" +#include "elna/source/semantic.hpp" #include #include #include @@ -42,11 +43,13 @@ namespace elna::cli } return 1; } - auto ast = source::parser(lex_result.success()).parse(); + auto ast = source::parser(std::move(lex_result.success())).parse(); if (ast == nullptr) { return 2; } + source::name_analysis_visitor().visit(ast.get()); + source::allocator_visitor().visit(ast.get()); riscv::riscv32_elf(ast.get(), out_file); return 0; diff --git a/include/elna/backend/riscv.hpp b/include/elna/backend/riscv.hpp index a5b33b8..63d4f1d 100644 --- a/include/elna/backend/riscv.hpp +++ b/include/elna/backend/riscv.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include "elna/source/parser.hpp" namespace elna::riscv @@ -163,13 +162,13 @@ namespace elna::riscv bool register_in_use{ true }; std::uint32_t variable_counter = 1; std::vector references; - std::unordered_map constants; + std::shared_ptr table; virtual void visit(source::declaration *declaration) override; virtual void visit(source::definition *definition) override; virtual void visit(source::bang_statement *statement) override; virtual void visit(source::compound_statement *statement) override; - virtual void visit(source::assignment_statement *statement) override; + virtual void visit(source::assign_statement *statement) override; virtual void visit(source::block *block) override; virtual void visit(source::variable_expression *variable) override; virtual void visit(source::integer_literal *number) override; diff --git a/include/elna/source/lexer.hpp b/include/elna/source/lexer.hpp index 4a0da00..f52bde0 100644 --- a/include/elna/source/lexer.hpp +++ b/include/elna/source/lexer.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include "elna/source/result.hpp" @@ -128,11 +129,93 @@ namespace elna::source std::string what() const override; }; + struct lexer + { + lexer(std::vector&& tokens, const position last_position); + lexer(const lexer&) = delete; + lexer(lexer&&) = default; + + lexer& operator=(const lexer&) = delete; + lexer& operator=(lexer&&) = default; + + lexer& operator++(); + const token& operator*() const; + const token *operator->() const; + + /** + * This function never fails and returns \ref token::type::eof at the + * end of the token stream. + * + * \return Current token. + */ + const token& current() const noexcept; + + /** + * \param token_type Token type. + * \return Whether the current token is \a token_type. + */ + bool current(const token::type token_type) const noexcept; + + /** + * Adds an \ref unexpected_token error to the error list. + * + * \param expected The token was expected. + */ + void add_error(const token& expected); + + /** + * Expects the current token to be \a token_type. In this case returns + * this token and advances to the next token in the stream. + * + * \param token_type Expected token type. + * \return Current token. + */ + std::optional> advance(const token::type token_type); + + /** + * Returns that follows the current token. If the current token is + * \ref token::type::eof, returns it. + * + * \return The token that follows the current one. + */ + const token& look_ahead() const; + + /** + * Tells whether the token following the current one is \a token_type. + * + * \param token_type Token type. + * \return Whether the next token is \a token_type. + */ + bool look_ahead(const token::type token_type) const; + + /** + * Skips one token if it is of type \a token_type. Adds an + * \ref unexpected_token error otherwise. + * + * \param token_type The token type was expected. + * \return Whether the current token was \a token_type. + */ + bool skip(const token::type token_type); + + /** + * Gets produced errors. + * + * \return Produced error list. + */ + const std::list>& errors() const noexcept; + + private: + std::vector tokens; + std::vector::const_iterator iterator; + std::list> m_errors; + token eof; + }; + /** - * Split the source into tokens. + * Splits the source text into tokens. * * \param buffer Source text. * \return Tokens or error. */ - elna::source::result> lex(const std::string& buffer); + elna::source::result lex(const std::string& buffer); } diff --git a/include/elna/source/parser.hpp b/include/elna/source/parser.hpp index b61cf00..1d63c70 100644 --- a/include/elna/source/parser.hpp +++ b/include/elna/source/parser.hpp @@ -1,9 +1,7 @@ #pragma once #include -#include #include -#include #include namespace elna::source @@ -20,7 +18,7 @@ namespace elna::source class definition; class bang_statement; class compound_statement; - class assignment_statement; + class assign_statement; class block; class binary_expression; class variable_expression; @@ -32,13 +30,26 @@ namespace elna::source virtual void visit(definition *) = 0; virtual void visit(bang_statement *) = 0; virtual void visit(compound_statement *) = 0; - virtual void visit(assignment_statement *) = 0; + virtual void visit(assign_statement *) = 0; virtual void visit(block *) = 0; virtual void visit(binary_expression *) = 0; virtual void visit(variable_expression *) = 0; virtual void visit(integer_literal *) = 0; }; + struct empty_visitor : parser_visitor + { + virtual void visit(declaration *declaration) override; + virtual void visit(definition *definition) override; + virtual void visit(bang_statement *statement) override; + virtual void visit(compound_statement *statement) override; + virtual void visit(assign_statement *statement) override; + virtual void visit(block *block) override; + virtual void visit(binary_expression *expression) override; + virtual void visit(variable_expression *variable) override; + virtual void visit(integer_literal *number) override; + }; + /** * AST node. */ @@ -109,11 +120,17 @@ namespace elna::source std::vector>& statements(); }; - class assignment_statement : public statement + class assign_statement : public statement { - std::unique_ptr lvalue; - std::unique_ptr rvalue; + std::string m_lvalue; + std::unique_ptr m_rvalue; + + public: + assign_statement(const std::string& lvalue, std::unique_ptr&& rvalue); virtual void accept(parser_visitor *visitor) override; + + std::string& lvalue() noexcept; + expression& rvalue(); }; /** @@ -124,6 +141,7 @@ namespace elna::source std::unique_ptr m_body; std::vector> m_definitions; std::vector> m_declarations; + std::shared_ptr m_table; public: block(std::vector>&& definitions, @@ -134,6 +152,7 @@ namespace elna::source statement& body(); std::vector>& definitions() noexcept; std::vector>& declarations() noexcept; + std::shared_ptr table(); }; class integer_literal : public expression @@ -174,13 +193,8 @@ namespace elna::source binary_operator operation() const noexcept; }; - struct parser : boost::noncopyable + class parser : boost::noncopyable { - parser(const std::vector& tokens); - - std::unique_ptr parse(); - - private: std::unique_ptr parse_factor(); std::unique_ptr parse_term(); std::unique_ptr parse_expression(); @@ -189,15 +203,28 @@ namespace elna::source std::unique_ptr parse_statement(); std::unique_ptr parse_bang_statement(); std::unique_ptr parse_compound_statement(); + std::unique_ptr parse_assign_statement(); std::vector> parse_definitions(); std::vector> parse_declarations(); std::unique_ptr parse_block(); - std::optional> advance(const token::type token_type); - bool skip(const token::type token_type); + lexer iterator; - std::vector::const_iterator tokens; - std::vector::const_iterator end; - std::list> errors; + public: + parser(lexer&& tokens); + + /** + * Parses a source text. + * + * \return Parsed program or nothing if an error occurred. + */ + std::unique_ptr parse(); + + /** + * Gets produced errors. + * + * \return Produced error list. + */ + const std::list>& errors() const noexcept; }; } diff --git a/include/elna/source/result.hpp b/include/elna/source/result.hpp index a0ea7b9..8cb3513 100644 --- a/include/elna/source/result.hpp +++ b/include/elna/source/result.hpp @@ -1,10 +1,13 @@ #pragma once #include +#include #include #include #include +#include #include +#include namespace elna::source { @@ -50,8 +53,8 @@ namespace elna::source using E = std::list>; template>> - explicit result(Args&&... arguments) - : payload(std::forward(arguments)...) + explicit result(std::in_place_t, Args&&... arguments) + : payload(std::in_place_type, std::forward(arguments)...) { } @@ -62,6 +65,7 @@ namespace elna::source template>> explicit result(U&& first) + : payload(E()) { errors().emplace_back(std::make_unique(first)); } @@ -89,4 +93,75 @@ namespace elna::source private: std::variant payload; }; + + class name_collision final : public error + { + position previous; + std::string name; + + public: + name_collision(const std::string& name, const position current, const position previous); + + std::string what() const override; + }; + + /** + * Generic language entity information. + */ + class info + { + public: + virtual ~info() = 0; + + protected: + info(); + }; + + /** + * Constant information. + */ + class constant_info final : public info + { + std::int32_t m_value; + + public: + constant_info(const std::int32_t value); + ~constant_info() override; + std::int32_t value() const noexcept; + }; + + /** + * Variable information. + */ + struct variable_info final : public info + { + std::ptrdiff_t offset{ 0 }; + + ~variable_info() override; + }; + + class procedure_info final : public info + { + std::size_t local_stack_size{ 0 }; + + public: + ~procedure_info() override; + + void stack_size(const std::size_t size) noexcept; + std::size_t stack_size() const noexcept; + }; + + /** + * Symbol table. + */ + class symbol_table + { + std::unordered_map> entries; + + public: + symbol_table() = default; + + std::shared_ptr lookup(const std::string& name); + void enter(const std::string& name, std::shared_ptr entry); + }; } diff --git a/include/elna/source/semantic.hpp b/include/elna/source/semantic.hpp new file mode 100644 index 0000000..6d2e311 --- /dev/null +++ b/include/elna/source/semantic.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "elna/source/parser.hpp" + +namespace elna::source +{ + class name_analysis_visitor final : public empty_visitor + { + std::shared_ptr table; + + public: + void visit(definition *definition) override; + void visit(declaration *declaration) override; + void visit(block *block) override; + }; + + class allocator_visitor final : public empty_visitor + { + std::ptrdiff_t offset; + + public: + void visit(declaration *declaration) override; + void visit(block *block) override; + }; +} diff --git a/include/elna/source/symboltable.hpp b/include/elna/source/symboltable.hpp deleted file mode 100644 index a5a59b3..0000000 --- a/include/elna/source/symboltable.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "elna/source/parser.hpp" -#include -#include - -namespace elna::source -{ - class name_collision final : public error - { - position previous; - std::string name; - - public: - name_collision(const std::string& name, const position current, const position previous); - - std::string what() const override; - }; - - class info - { - public: - virtual ~info() = 0; - - protected: - info(); - }; - - class constant_info final : public info - { - std::int32_t m_value; - - public: - constant_info(const std::int32_t value); - ~constant_info() override; - std::int32_t value() const noexcept; - }; - - class variable_info final : public info - { - std::size_t m_offset{ 0 }; - - public: - variable_info(const std::size_t offset); - ~variable_info() override; - - std::size_t offset() const noexcept; - }; - - class symbol_table - { - std::unordered_map> entries; - - public: - symbol_table() = default; - - std::shared_ptr lookup(const std::string& name); - void enter(const std::string& name, std::shared_ptr entry); - }; - - class name_analysis_visitor final : public source::parser_visitor - { - void visit(declaration *declaration) override; - void visit(definition *definition) override; - void visit(bang_statement *statement) override; - void visit(compound_statement *statement) override; - void visit(assignment_statement *statement) override; - void visit(block *block) override; - void visit(integer_literal *number) override; - void visit(variable_expression *variable) override; - void visit(binary_expression *expression) override; - }; -} diff --git a/include/elna/tester.hpp b/include/elna/tester.hpp index ee34351..1690a33 100644 --- a/include/elna/tester.hpp +++ b/include/elna/tester.hpp @@ -1,9 +1,28 @@ #pragma once #include +#include +#include + +#define BOOST_PROCESS_USE_STD_FS namespace elna { + enum class test_status + { + successful, + compile_failed, + build_failed, + expectation_failed, + }; + + struct test_result final + { + test_status status{ test_status::successful }; + int code{ 0 }; + std::string output; + }; + class test_results final { std::uint32_t m_total{ 0 }; @@ -17,6 +36,8 @@ namespace elna std::uint32_t failed() const noexcept; int exit_code() const noexcept; - void add_exit_code(const int exit_code) noexcept; + void add_exit_code(const test_status result) noexcept; }; + + void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result); } diff --git a/source/lexer.cpp b/source/lexer.cpp index 95f193e..d509273 100644 --- a/source/lexer.cpp +++ b/source/lexer.cpp @@ -6,7 +6,6 @@ namespace elna::source { using source_position = elna::source::position; using source_error = elna::source::error; - using source_result = elna::source::result>; std::pair text_iterators(const std::string &buffer) { @@ -225,7 +224,83 @@ namespace elna::source return "Unexpected token"; } - source_result lex(const std::string& buffer) + 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::operator++() + { + ++iterator; + return *this; + } + + const token& lexer::operator*() const + { + return *iterator; + } + + const token *lexer::operator->() const + { + return iterator.base(); + } + + const token& lexer::current() const noexcept + { + if (iterator == tokens.cend()) + { + return this->eof; + } + return *iterator; + } + + bool lexer::current(const token::type token_type) const noexcept + { + return current().of() == token_type; + } + + void lexer::add_error(const token& expected) + { + m_errors.push_back(std::make_unique(expected)); + } + + std::optional> lexer::advance(const token::type token_type) + { + if (iterator != tokens.cend() && iterator->of() == token_type) + { + return std::make_optional<>(std::cref(*iterator++)); + } + add_error(current()); + return std::optional>(); + } + + const token& lexer::look_ahead() const + { + auto tmp = iterator; + ++tmp; + if (iterator == tokens.cend() || tmp == tokens.cend()) + { + return eof; + } + return *tmp; + } + + bool lexer::look_ahead(const token::type token_type) const + { + return look_ahead().of() == token_type; + } + + bool lexer::skip(const token::type token_type) + { + return advance(token_type).has_value(); + } + + const std::list>& lexer::errors() const noexcept + { + return m_errors; + } + + result lex(const std::string& buffer) { std::vector tokens; auto [iterator, text_end] = text_iterators(buffer); @@ -322,12 +397,10 @@ namespace elna::source } else { - return source_result(unexpected_character{ std::string{ *iterator }, iterator.position() }); + return result(unexpected_character{ std::string{ *iterator }, iterator.position() }); } ++iterator; } - tokens.push_back(token(token::type::eof, iterator.position())); - - return source_result(std::move(tokens)); + return result(std::in_place, std::move(tokens), iterator.position()); } } diff --git a/source/parser.cpp b/source/parser.cpp index 5a58501..77f20bb 100644 --- a/source/parser.cpp +++ b/source/parser.cpp @@ -3,6 +3,60 @@ namespace elna::source { + void empty_visitor::visit(declaration *declaration) + { + } + + void empty_visitor::visit(definition *definition) + { + definition->body().accept(this); + } + + void empty_visitor::visit(bang_statement *statement) + { + statement->body().accept(this); + } + + void empty_visitor::visit(compound_statement *statement) + { + for (auto& nested_statement : statement->statements()) + { + nested_statement->accept(this); + } + } + + void empty_visitor::visit(assign_statement *statement) + { + statement->rvalue().accept(this); + } + + void empty_visitor::visit(block *block) + { + for (const auto& block_definition : block->definitions()) + { + block_definition->accept(this); + } + for (const auto& block_declaration : block->declarations()) + { + block_declaration->accept(this); + } + block->body().accept(this); + } + + void empty_visitor::visit(binary_expression *expression) + { + expression->lhs().accept(this); + expression->rhs().accept(this); + } + + void empty_visitor::visit(variable_expression *variable) + { + } + + void empty_visitor::visit(integer_literal *number) + { + } + /** * AST node. */ @@ -48,7 +102,9 @@ namespace elna::source block::block(std::vector>&& definitions, std::vector>&& declarations, std::unique_ptr&& body) - : m_definitions(std::move(definitions)), m_declarations(std::move(declarations)), m_body(std::move(body)) + : m_definitions(std::move(definitions)), + m_declarations(std::move(declarations)), m_body(std::move(body)), + m_table(std::make_shared()) { } @@ -72,6 +128,11 @@ namespace elna::source return m_declarations; } + std::shared_ptr block::table() + { + return m_table; + } + integer_literal::integer_literal(const std::int32_t value) : m_number(value) { @@ -175,13 +236,28 @@ namespace elna::source return m_statements; } - void assignment_statement::accept(parser_visitor *visitor) + void assign_statement::accept(parser_visitor *visitor) { visitor->visit(this); } - parser::parser(const std::vector& tokens) - : tokens(tokens.cbegin()), end(tokens.cend()) + assign_statement::assign_statement(const std::string& lvalue, std::unique_ptr&& rvalue) + : m_lvalue(lvalue), m_rvalue(std::move(rvalue)) + { + } + + std::string& assign_statement::lvalue() noexcept + { + return m_lvalue; + } + + expression& assign_statement::rvalue() + { + return *m_rvalue; + } + + parser::parser(lexer&& tokens) + : iterator(std::move(tokens)) { } @@ -190,27 +266,32 @@ namespace elna::source return parse_block(); } + const std::list>& parser::errors() const noexcept + { + return iterator.errors(); + } + std::unique_ptr parser::parse_factor() { - if (tokens->of() == source::token::type::identifier) + if (iterator->of() == source::token::type::identifier) { - auto result = std::make_unique(tokens->identifier()); - ++tokens; + auto result = std::make_unique(iterator->identifier()); + ++iterator; return result; } - else if (tokens->of() == source::token::token::type::number) + else if (iterator->of() == source::token::token::type::number) { - auto result = std::make_unique(tokens->number()); - ++tokens; + auto result = std::make_unique(iterator->number()); + ++iterator; return result; } - else if (tokens->of() == source::token::type::left_paren) + else if (iterator->of() == source::token::type::left_paren) { - ++tokens; + ++iterator; auto expression = parse_expression(); - ++tokens; + ++iterator; return expression; } @@ -220,14 +301,14 @@ namespace elna::source std::unique_ptr parser::parse_term() { auto lhs = parse_factor(); - if (lhs == nullptr || tokens == end || tokens->of() != source::token::type::factor_operator) + if (lhs == nullptr || iterator.current().of() != source::token::type::factor_operator) { return lhs; } - while (tokens->of() == source::token::type::factor_operator) + while (iterator->of() == source::token::type::factor_operator) { - auto _operator = tokens->identifier()[0]; - ++tokens; + auto _operator = iterator->identifier()[0]; + ++iterator; auto rhs = parse_factor(); lhs = std::make_unique(std::move(lhs), @@ -239,14 +320,14 @@ namespace elna::source std::unique_ptr parser::parse_expression() { auto term = parse_term(); - if (term == nullptr || tokens == end || tokens->of() != source::token::type::term_operator) + if (term == nullptr || iterator.current().of() != source::token::type::term_operator) { return term; } - while (tokens->of() == source::token::type::term_operator) + while (iterator->of() == source::token::type::term_operator) { - auto _operator = tokens->identifier()[0]; - ++tokens; + auto _operator = iterator->identifier()[0]; + ++iterator; auto rhs = parse_term(); term = std::make_unique(std::move(term), @@ -257,22 +338,22 @@ namespace elna::source std::unique_ptr parser::parse_definition() { - auto definition_identifier = advance(token::type::identifier); + auto definition_identifier = iterator.advance(token::type::identifier); if (!definition_identifier.has_value()) { return nullptr; } - if (!skip(token::type::equals)) + if (!iterator.skip(token::type::equals)) { return nullptr; } - if (tokens->of() == source::token::type::number) + if (iterator->of() == source::token::type::number) { auto result = std::make_unique(definition_identifier.value().get().identifier(), - std::make_unique(tokens->number())); - ++tokens; + std::make_unique(iterator->number())); + ++iterator; return result; } return nullptr; @@ -280,7 +361,7 @@ namespace elna::source std::unique_ptr parser::parse_declaration() { - auto declaration_identifier = advance(token::type::identifier); + auto declaration_identifier = iterator.advance(token::type::identifier); if (!declaration_identifier.has_value()) { @@ -291,21 +372,25 @@ namespace elna::source std::unique_ptr parser::parse_statement() { - if (tokens->of() == source::token::type::bang) + if (iterator.look_ahead(token::type::assignment)) + { + return parse_assign_statement(); + } + else if (iterator.current(token::type::bang)) { return parse_bang_statement(); } - else if (tokens->of() == source::token::type::begin) + else if (iterator.current(token::type::begin)) { return parse_compound_statement(); } - errors.push_back(std::make_unique(unexpected_token{ *tokens })); + iterator.add_error(*iterator); return nullptr; } std::unique_ptr parser::parse_bang_statement() { - if (!advance(token::type::bang)) + if (!iterator.advance(token::type::bang)) { return nullptr; } @@ -321,7 +406,7 @@ namespace elna::source std::unique_ptr parser::parse_compound_statement() { - if (!advance(token::type::begin)) + if (!iterator.advance(token::type::begin)) { return nullptr; } @@ -332,18 +417,18 @@ namespace elna::source { result->statements().push_back(std::move(next_statement)); - if (tokens->of() == token::type::semicolon) + if (iterator->of() == token::type::semicolon) { - ++tokens; + ++iterator; } - else if (tokens->of() == token::type::end) + else if (iterator->of() == token::type::end) { - ++tokens; + ++iterator; break; } else { - errors.push_back(std::make_unique(*tokens)); + iterator.add_error(*iterator); break; } } @@ -351,33 +436,49 @@ namespace elna::source return result; } + std::unique_ptr parser::parse_assign_statement() + { + auto name = iterator.advance(token::type::identifier); + if (!name.has_value() || !iterator.skip(token::type::assignment)) + { + return nullptr; + } + auto rvalue = parse_expression(); + + if (rvalue == nullptr) + { + return nullptr; + } + return std::make_unique(name.value().get().identifier(), std::move(rvalue)); + } + std::vector> parser::parse_definitions() { std::vector> definitions; - if (tokens->of() != token::type::let) + if (iterator->of() != token::type::let) { return definitions; } - ++tokens; // Skip const. + ++iterator; // Skip const. std::unique_ptr parsed_definition; while ((parsed_definition = parse_definition()) != nullptr) { definitions.push_back(std::move(parsed_definition)); - if (tokens->of() == source::token::type::comma) + if (iterator->of() == source::token::type::comma) { - ++tokens; + ++iterator; } - else if (tokens->of() == source::token::type::semicolon) + else if (iterator->of() == source::token::type::semicolon) { - ++tokens; + ++iterator; break; } else { - errors.push_back(std::make_unique(*tokens)); + iterator.add_error(*iterator); break; } } @@ -388,29 +489,29 @@ namespace elna::source { std::vector> declarations; - if (tokens->of() != token::type::var) + if (iterator->of() != token::type::var) { return declarations; } - ++tokens; // Skip var. + ++iterator; // Skip var. std::unique_ptr parsed_declaration; while ((parsed_declaration = parse_declaration()) != nullptr) { declarations.push_back(std::move(parsed_declaration)); - if (tokens->of() == token::type::comma) + if (iterator->of() == token::type::comma) { - ++tokens; + ++iterator; } - else if (tokens->of() == token::type::semicolon) + else if (iterator->of() == token::type::semicolon) { - ++tokens; + ++iterator; break; } else { - errors.push_back(std::make_unique(*tokens)); + iterator.add_error(*iterator); break; } } @@ -430,25 +531,4 @@ namespace elna::source return std::make_unique(std::move(definitions), std::move(declarations), std::move(parsed_statement)); } - - std::optional> parser::advance(const token::type token_type) - { - if (tokens->of() == token_type) - { - return std::make_optional<>(std::cref(*tokens++)); - } - errors.push_back(std::make_unique(*tokens)); - return std::optional>(); - } - - bool parser::skip(const token::type token_type) - { - if (tokens->of() == token_type) - { - ++tokens; - return true; - } - errors.push_back(std::make_unique(*tokens)); - return false; - } } diff --git a/source/result.cpp b/source/result.cpp index 42f53fe..0d989d1 100644 --- a/source/result.cpp +++ b/source/result.cpp @@ -16,4 +16,72 @@ 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) + { + } + + std::string name_collision::what() const + { + return "Name '" + name + "' was already defined"; + } + + std::shared_ptr symbol_table::lookup(const std::string& name) + { + auto entry = entries.find(name); + if (entry == entries.cend()) + { + return nullptr; + } + else + { + return entry->second; + } + } + + void symbol_table::enter(const std::string& name, std::shared_ptr entry) + { + entries.insert_or_assign(name, entry); + } + + info::~info() + { + } + + info::info() + { + } + + constant_info::constant_info(const std::int32_t value) + : m_value(value) + { + } + + constant_info::~constant_info() + { + } + + std::int32_t constant_info::value() const noexcept + { + return m_value; + } + + variable_info::~variable_info() + { + } + + procedure_info::~procedure_info() + { + } + + std::size_t procedure_info::stack_size() const noexcept + { + return this->local_stack_size; + } + + void procedure_info::stack_size(const std::size_t size) noexcept + { + this->local_stack_size = size; + } } diff --git a/source/semantic.cpp b/source/semantic.cpp new file mode 100644 index 0000000..bd0eb94 --- /dev/null +++ b/source/semantic.cpp @@ -0,0 +1,38 @@ +#include "elna/source/semantic.hpp" +#include + +namespace elna::source +{ + void name_analysis_visitor::visit(definition *definition) + { + this->table->enter(definition->identifier(), + std::make_shared(constant_info(definition->body().number()))); + } + + void name_analysis_visitor::visit(declaration *declaration) + { + this->table->enter(declaration->identifier(), + std::make_shared(variable_info())); + } + + void name_analysis_visitor::visit(block *block) + { + this->table = block->table(); + empty_visitor::visit(block); + this->table->enter("main", + std::make_shared()); + } + + void allocator_visitor::visit(declaration *declaration) + { + this->offset -= sizeof(std::int32_t); + } + + void allocator_visitor::visit(block *block) + { + this->offset = 0; + empty_visitor::visit(block); + std::dynamic_pointer_cast(block->table()->lookup("main")) + ->stack_size(std::abs(this->offset)); + } +} diff --git a/source/symboltable.cpp b/source/symboltable.cpp deleted file mode 100644 index ee3b710..0000000 --- a/source/symboltable.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "elna/source/symboltable.hpp" - -namespace elna::source -{ - name_collision::name_collision(const std::string& name, const position current, const position previous) - : error(current), name(name), previous(previous) - { - } - - std::string name_collision::what() const - { - return "Name '" + name + "' was already defined"; - } - - std::shared_ptr symbol_table::lookup(const std::string& name) - { - auto entry = entries.find(name); - if (entry == entries.cend()) - { - return nullptr; - } - else - { - return entry->second; - } - } - - void symbol_table::enter(const std::string& name, std::shared_ptr entry) - { - entries.insert_or_assign(name, entry); - } - - info::~info() - { - } - - info::info() - { - } - - constant_info::constant_info(const std::int32_t value) - : m_value(value) - { - } - - constant_info::~constant_info() - { - } - - std::int32_t constant_info::value() const noexcept - { - return m_value; - } - - variable_info::variable_info(std::size_t offset) - : m_offset(offset) - { - } - - variable_info::~variable_info() - { - } - - std::size_t variable_info::offset() const noexcept - { - return m_offset; - } - - void name_analysis_visitor::visit(declaration *declaration) - { - } - - void name_analysis_visitor::visit(definition *definition) - { - } - - void name_analysis_visitor::visit(bang_statement *statement) - { - } - - void name_analysis_visitor::visit(compound_statement *statement) - { - } - - void name_analysis_visitor::visit(assignment_statement *statement) - { - } - - void name_analysis_visitor::visit(block *block) - { - } - - void name_analysis_visitor::visit(integer_literal *number) - { - } - - void name_analysis_visitor::visit(variable_expression *variable) - { - } - - void name_analysis_visitor::visit(binary_expression *expression) - { - } -} diff --git a/tests/declare_variable.eln b/tests/declare_variable.eln index 4f7131f..4d2d89b 100644 --- a/tests/declare_variable.eln +++ b/tests/declare_variable.eln @@ -1,5 +1,5 @@ var x; begin x := 5; - ! 5 + ! x end. diff --git a/tests/tester.cpp b/tests/tester.cpp index bd9eadf..2c54ade 100755 --- a/tests/tester.cpp +++ b/tests/tester.cpp @@ -1,9 +1,11 @@ -#include - -#include -#include #include "elna/tester.hpp" +#include +#include + +#include +#include + namespace elna { std::uint32_t test_results::total() const noexcept @@ -26,10 +28,10 @@ namespace elna return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE; } - void test_results::add_exit_code(const int exit_code) noexcept + void test_results::add_exit_code(const test_status status) noexcept { ++m_total; - if (exit_code == 0) + if (status == test_status::successful) { ++m_passed; } @@ -45,7 +47,7 @@ namespace elna return in_build_directory() / path; } - static int build_test(const std::filesystem::directory_entry& test_entry) + static test_result build_test(const std::filesystem::directory_entry& test_entry) { const std::filesystem::path test_filename = test_entry.path().filename(); @@ -61,7 +63,7 @@ namespace elna "-o", test_object.string(), test_entry.path().string()); if (status != 0) { - return status; + return test_result{ test_status::compile_failed, status }; } status = boost::process::system( "/opt/riscv/bin/riscv32-unknown-elf-ld", @@ -74,37 +76,55 @@ namespace elna "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group", "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o" ); - return status; + return test_result{}; } - static int run_test(const std::filesystem::directory_entry& test_entry) + static test_result check_expectation(const std::filesystem::directory_entry& test_entry, const test_result& run) + { + const std::filesystem::path test_filename = test_entry.path().filename(); + + std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; + expectation_path.replace_extension(".txt"); + + boost::process::opstream pipe_stream; + boost::process::child diff( + boost::process::search_path("diff"), "-Nur", "--color", + expectation_path.string(), "-", + boost::process::std_in < pipe_stream + ); + + pipe_stream << run.output; + pipe_stream.flush(); + pipe_stream.pipe().close(); + diff.wait(); + + if (diff.exit_code() == 0) + { + return run; + } + return test_result{ test_status::expectation_failed, run.code, run.output }; + } + + static test_result run_test(const std::filesystem::directory_entry& test_entry) { const std::filesystem::path test_filename = test_entry.path().filename(); std::filesystem::path test_binary = in_build_directory(test_filename); test_binary.replace_extension(); - std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; - expectation_path.replace_extension(".txt"); - - std::cout << "Running " << test_binary << std::endl; - - boost::process::pipe pipe_stream; - boost::process::child vm( + boost::asio::io_service io_service; + std::future buffer; + boost::process::child spike( "/opt/riscv/bin/spike", "--isa=RV32IMAC", "/opt/riscv/riscv32-unknown-elf/bin/pk", test_binary.string(), - boost::process::std_out > pipe_stream + boost::process::std_out > buffer, + boost::process::std_err > buffer, + io_service ); - boost::process::child diff( - "/usr/bin/diff", "-Nur", "--color", - expectation_path.string(), "-", - boost::process::std_in < pipe_stream - ); - vm.wait(); - diff.wait(); + io_service.run(); - return diff.exit_code(); + return test_result{ test_status::successful, spike.exit_code(), buffer.get() }; } static test_results run_in_path(const std::filesystem::path test_directory) @@ -117,26 +137,37 @@ namespace elna { continue; } - int status{ 0 }; + test_result result; + std::cout << "Running " << test_entry << std::endl; try { - status = build_test(test_entry); + result = build_test(test_entry); + if (result.status == test_status::successful) + { + result = run_test(test_entry); + } + result = check_expectation(test_entry, result); } catch (const boost::process::process_error& exception) { - std::cout << exception.what() << std::endl; - status = 3; - continue; + result.status = test_status::build_failed; + result.output = exception.what(); + std::cout << result.output << std::endl; } - if (status == 0) - { - status = run_test(test_entry); - } - results.add_exit_code(status); + print_result(test_entry, result); + results.add_exit_code(result.status); } return results; } + + void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result) + { + if (result.status != test_status::successful) + { + std::cout << test_entry << " failed." << std::endl; + } + } }; int main()