Assign variables

This commit is contained in:
Eugen Wissner 2024-03-14 08:52:45 +01:00
parent 42d2038c4d
commit 8240443cd1
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
18 changed files with 700 additions and 339 deletions

View File

@ -22,7 +22,7 @@ add_executable(elna cli/main.cpp
source/lexer.cpp include/elna/source/lexer.hpp source/lexer.cpp include/elna/source/lexer.hpp
source/parser.cpp include/elna/source/parser.hpp source/parser.cpp include/elna/source/parser.hpp
source/result.cpp include/elna/source/result.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/riscv.cpp include/elna/backend/riscv.hpp
backend/target.cpp include/elna/backend/target.hpp backend/target.cpp include/elna/backend/target.hpp
cli/cl.cpp include/elna/cli/cl.hpp cli/cl.cpp include/elna/cli/cl.hpp

10
TODO
View File

@ -5,13 +5,15 @@
- Parser should be able to collect errors. - Parser should be able to collect errors.
- Provide position information on parse tree nodes. - Provide position information on parse tree nodes.
- Move constants to the symbol table, so we can check at parse time for duplicates. - 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. - 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. - While loop.
- If condition. - 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 # Shell
- Persist the history. - Persist the history.

View File

@ -63,7 +63,6 @@ namespace elna::riscv
void visitor::visit(source::definition *definition) void visitor::visit(source::definition *definition)
{ {
constants[definition->identifier()] = definition->body().number();
} }
void visitor::visit(source::block *block) 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::store));
this->instructions.push_back(instruction(base_opcode::opImm)); this->instructions.push_back(instruction(base_opcode::opImm));
for (const auto& block_definition : block->definitions()) table = block->table();
{
block_definition->accept(this);
}
for (const auto& block_declaration : block->declarations())
{
block_declaration->accept(this);
}
block->body().accept(this); block->body().accept(this);
// Set the return value (0). // 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)); .r(x_register::a0, funct3_t::_and, x_register::zero, x_register::zero));
// Prologue. // Prologue.
const uint stack_size = static_cast<std::uint32_t>(variable_counter * 4 + 12); auto main_symbol =
std::dynamic_pointer_cast<source::procedure_info>(table->lookup("main"));
const uint stack_size = static_cast<std::uint32_t>(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[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); this->instructions[1].s(stack_size - 4, funct3_t::sw, x_register::sp, x_register::s0);
@ -143,19 +137,38 @@ 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<source::variable_info>(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) void visitor::visit(source::variable_expression *variable)
{ {
const auto free_register = this->register_in_use ? x_register::a0 : x_register::t0; const auto free_register = this->register_in_use ? x_register::a0 : x_register::t0;
auto symbol = table->lookup(variable->name());
if (auto constant_symbol = std::dynamic_pointer_cast<source::constant_info>(symbol))
{
this->instructions.push_back( this->instructions.push_back(
instruction(base_opcode::opImm) // movl $x, %eax; where $x is a number. instruction(base_opcode::opImm) // movl $x, %eax; where $x is a number.
.i(free_register, funct3_t::addi, x_register::zero, constants[variable->name()]) .i(free_register, funct3_t::addi, x_register::zero, constant_symbol->value())
); );
} }
else if (auto variable_symbol = std::dynamic_pointer_cast<source::variable_info>(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) void visitor::visit(source::integer_literal *number)
{ {

View File

@ -1,5 +1,6 @@
#include "elna/cli/cl.hpp" #include "elna/cli/cl.hpp"
#include "elna/backend/target.hpp" #include "elna/backend/target.hpp"
#include "elna/source/semantic.hpp"
#include <cstddef> #include <cstddef>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@ -42,11 +43,13 @@ namespace elna::cli
} }
return 1; return 1;
} }
auto ast = source::parser(lex_result.success()).parse(); auto ast = source::parser(std::move(lex_result.success())).parse();
if (ast == nullptr) if (ast == nullptr)
{ {
return 2; return 2;
} }
source::name_analysis_visitor().visit(ast.get());
source::allocator_visitor().visit(ast.get());
riscv::riscv32_elf(ast.get(), out_file); riscv::riscv32_elf(ast.get(), out_file);
return 0; return 0;

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <unordered_map>
#include "elna/source/parser.hpp" #include "elna/source/parser.hpp"
namespace elna::riscv namespace elna::riscv
@ -163,13 +162,13 @@ namespace elna::riscv
bool register_in_use{ true }; bool register_in_use{ true };
std::uint32_t variable_counter = 1; std::uint32_t variable_counter = 1;
std::vector<reference> references; std::vector<reference> references;
std::unordered_map<std::string, std::int32_t> constants; std::shared_ptr<source::symbol_table> table;
virtual void visit(source::declaration *declaration) override; virtual void visit(source::declaration *declaration) override;
virtual void visit(source::definition *definition) override; virtual void visit(source::definition *definition) override;
virtual void visit(source::bang_statement *statement) override; virtual void visit(source::bang_statement *statement) override;
virtual void visit(source::compound_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::block *block) override;
virtual void visit(source::variable_expression *variable) override; virtual void visit(source::variable_expression *variable) override;
virtual void visit(source::integer_literal *number) override; virtual void visit(source::integer_literal *number) override;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include "elna/source/result.hpp" #include "elna/source/result.hpp"
@ -128,11 +129,93 @@ namespace elna::source
std::string what() const override; std::string what() const override;
}; };
struct lexer
{
lexer(std::vector<token>&& 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;
/** /**
* Split the source into tokens. * 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<std::reference_wrapper<const token>> 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<std::unique_ptr<error>>& errors() const noexcept;
private:
std::vector<token> tokens;
std::vector<token>::const_iterator iterator;
std::list<std::unique_ptr<error>> m_errors;
token eof;
};
/**
* Splits the source text into tokens.
* *
* \param buffer Source text. * \param buffer Source text.
* \return Tokens or error. * \return Tokens or error.
*/ */
elna::source::result<std::vector<token>> lex(const std::string& buffer); elna::source::result<lexer> lex(const std::string& buffer);
} }

View File

@ -1,9 +1,7 @@
#pragma once #pragma once
#include <boost/core/noncopyable.hpp> #include <boost/core/noncopyable.hpp>
#include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <elna/source/lexer.hpp> #include <elna/source/lexer.hpp>
namespace elna::source namespace elna::source
@ -20,7 +18,7 @@ namespace elna::source
class definition; class definition;
class bang_statement; class bang_statement;
class compound_statement; class compound_statement;
class assignment_statement; class assign_statement;
class block; class block;
class binary_expression; class binary_expression;
class variable_expression; class variable_expression;
@ -32,13 +30,26 @@ namespace elna::source
virtual void visit(definition *) = 0; virtual void visit(definition *) = 0;
virtual void visit(bang_statement *) = 0; virtual void visit(bang_statement *) = 0;
virtual void visit(compound_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(block *) = 0;
virtual void visit(binary_expression *) = 0; virtual void visit(binary_expression *) = 0;
virtual void visit(variable_expression *) = 0; virtual void visit(variable_expression *) = 0;
virtual void visit(integer_literal *) = 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. * AST node.
*/ */
@ -109,11 +120,17 @@ namespace elna::source
std::vector<std::unique_ptr<statement>>& statements(); std::vector<std::unique_ptr<statement>>& statements();
}; };
class assignment_statement : public statement class assign_statement : public statement
{ {
std::unique_ptr<variable_expression> lvalue; std::string m_lvalue;
std::unique_ptr<expression> rvalue; std::unique_ptr<expression> m_rvalue;
public:
assign_statement(const std::string& lvalue, std::unique_ptr<expression>&& rvalue);
virtual void accept(parser_visitor *visitor) override; virtual void accept(parser_visitor *visitor) override;
std::string& lvalue() noexcept;
expression& rvalue();
}; };
/** /**
@ -124,6 +141,7 @@ namespace elna::source
std::unique_ptr<statement> m_body; std::unique_ptr<statement> m_body;
std::vector<std::unique_ptr<definition>> m_definitions; std::vector<std::unique_ptr<definition>> m_definitions;
std::vector<std::unique_ptr<declaration>> m_declarations; std::vector<std::unique_ptr<declaration>> m_declarations;
std::shared_ptr<symbol_table> m_table;
public: public:
block(std::vector<std::unique_ptr<definition>>&& definitions, block(std::vector<std::unique_ptr<definition>>&& definitions,
@ -134,6 +152,7 @@ namespace elna::source
statement& body(); statement& body();
std::vector<std::unique_ptr<definition>>& definitions() noexcept; std::vector<std::unique_ptr<definition>>& definitions() noexcept;
std::vector<std::unique_ptr<declaration>>& declarations() noexcept; std::vector<std::unique_ptr<declaration>>& declarations() noexcept;
std::shared_ptr<symbol_table> table();
}; };
class integer_literal : public expression class integer_literal : public expression
@ -174,13 +193,8 @@ namespace elna::source
binary_operator operation() const noexcept; binary_operator operation() const noexcept;
}; };
struct parser : boost::noncopyable class parser : boost::noncopyable
{ {
parser(const std::vector<token>& tokens);
std::unique_ptr<block> parse();
private:
std::unique_ptr<expression> parse_factor(); std::unique_ptr<expression> parse_factor();
std::unique_ptr<expression> parse_term(); std::unique_ptr<expression> parse_term();
std::unique_ptr<expression> parse_expression(); std::unique_ptr<expression> parse_expression();
@ -189,15 +203,28 @@ namespace elna::source
std::unique_ptr<statement> parse_statement(); std::unique_ptr<statement> parse_statement();
std::unique_ptr<bang_statement> parse_bang_statement(); std::unique_ptr<bang_statement> parse_bang_statement();
std::unique_ptr<compound_statement> parse_compound_statement(); std::unique_ptr<compound_statement> parse_compound_statement();
std::unique_ptr<assign_statement> parse_assign_statement();
std::vector<std::unique_ptr<definition>> parse_definitions(); std::vector<std::unique_ptr<definition>> parse_definitions();
std::vector<std::unique_ptr<declaration>> parse_declarations(); std::vector<std::unique_ptr<declaration>> parse_declarations();
std::unique_ptr<block> parse_block(); std::unique_ptr<block> parse_block();
std::optional<std::reference_wrapper<const token>> advance(const token::type token_type); lexer iterator;
bool skip(const token::type token_type);
std::vector<token>::const_iterator tokens; public:
std::vector<token>::const_iterator end; parser(lexer&& tokens);
std::list<std::unique_ptr<error>> errors;
/**
* Parses a source text.
*
* \return Parsed program or nothing if an error occurred.
*/
std::unique_ptr<block> parse();
/**
* Gets produced errors.
*
* \return Produced error list.
*/
const std::list<std::unique_ptr<error>>& errors() const noexcept;
}; };
} }

View File

@ -1,10 +1,13 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <variant> #include <variant>
#include <list> #include <list>
#include <memory> #include <memory>
#include <string>
#include <type_traits> #include <type_traits>
#include <unordered_map>
namespace elna::source namespace elna::source
{ {
@ -50,8 +53,8 @@ namespace elna::source
using E = std::list<std::unique_ptr<source::error>>; using E = std::list<std::unique_ptr<source::error>>;
template<typename... Args, typename = std::enable_if<std::is_constructible_v<T, Args...>>> template<typename... Args, typename = std::enable_if<std::is_constructible_v<T, Args...>>>
explicit result(Args&&... arguments) explicit result(std::in_place_t, Args&&... arguments)
: payload(std::forward<Args>(arguments)...) : payload(std::in_place_type<T>, std::forward<Args>(arguments)...)
{ {
} }
@ -62,6 +65,7 @@ namespace elna::source
template<typename U, typename = std::enable_if<std::is_base_of_v<error, U>>> template<typename U, typename = std::enable_if<std::is_base_of_v<error, U>>>
explicit result(U&& first) explicit result(U&& first)
: payload(E())
{ {
errors().emplace_back(std::make_unique<U>(first)); errors().emplace_back(std::make_unique<U>(first));
} }
@ -89,4 +93,75 @@ namespace elna::source
private: private:
std::variant<T, E> payload; std::variant<T, E> 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<std::string, std::shared_ptr<info>> entries;
public:
symbol_table() = default;
std::shared_ptr<info> lookup(const std::string& name);
void enter(const std::string& name, std::shared_ptr<info> entry);
};
} }

View File

@ -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<symbol_table> 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;
};
}

View File

@ -1,73 +0,0 @@
#pragma once
#include "elna/source/parser.hpp"
#include <unordered_map>
#include <memory>
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<std::string, std::shared_ptr<info>> entries;
public:
symbol_table() = default;
std::shared_ptr<info> lookup(const std::string& name);
void enter(const std::string& name, std::shared_ptr<info> 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;
};
}

View File

@ -1,9 +1,28 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <string>
#include <filesystem>
#define BOOST_PROCESS_USE_STD_FS
namespace elna 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 class test_results final
{ {
std::uint32_t m_total{ 0 }; std::uint32_t m_total{ 0 };
@ -17,6 +36,8 @@ namespace elna
std::uint32_t failed() const noexcept; std::uint32_t failed() const noexcept;
int exit_code() 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);
} }

View File

@ -6,7 +6,6 @@ namespace elna::source
{ {
using source_position = elna::source::position; using source_position = elna::source::position;
using source_error = elna::source::error; using source_error = elna::source::error;
using source_result = elna::source::result<std::vector<token>>;
std::pair<text_iterator, text_iterator> text_iterators(const std::string &buffer) std::pair<text_iterator, text_iterator> text_iterators(const std::string &buffer)
{ {
@ -225,7 +224,83 @@ namespace elna::source
return "Unexpected token"; return "Unexpected token";
} }
source_result lex(const std::string& buffer) lexer::lexer(std::vector<token>&& 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<unexpected_token>(expected));
}
std::optional<std::reference_wrapper<const token>> 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<std::reference_wrapper<const token>>();
}
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<std::unique_ptr<error>>& lexer::errors() const noexcept
{
return m_errors;
}
result<lexer> lex(const std::string& buffer)
{ {
std::vector<token> tokens; std::vector<token> tokens;
auto [iterator, text_end] = text_iterators(buffer); auto [iterator, text_end] = text_iterators(buffer);
@ -322,12 +397,10 @@ namespace elna::source
} }
else else
{ {
return source_result(unexpected_character{ std::string{ *iterator }, iterator.position() }); return result<lexer>(unexpected_character{ std::string{ *iterator }, iterator.position() });
} }
++iterator; ++iterator;
} }
tokens.push_back(token(token::type::eof, iterator.position())); return result<lexer>(std::in_place, std::move(tokens), iterator.position());
return source_result(std::move(tokens));
} }
} }

View File

@ -3,6 +3,60 @@
namespace elna::source 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. * AST node.
*/ */
@ -48,7 +102,9 @@ namespace elna::source
block::block(std::vector<std::unique_ptr<definition>>&& definitions, block::block(std::vector<std::unique_ptr<definition>>&& definitions,
std::vector<std::unique_ptr<declaration>>&& declarations, std::vector<std::unique_ptr<declaration>>&& declarations,
std::unique_ptr<statement>&& body) std::unique_ptr<statement>&& 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<symbol_table>())
{ {
} }
@ -72,6 +128,11 @@ namespace elna::source
return m_declarations; return m_declarations;
} }
std::shared_ptr<symbol_table> block::table()
{
return m_table;
}
integer_literal::integer_literal(const std::int32_t value) integer_literal::integer_literal(const std::int32_t value)
: m_number(value) : m_number(value)
{ {
@ -175,13 +236,28 @@ namespace elna::source
return m_statements; return m_statements;
} }
void assignment_statement::accept(parser_visitor *visitor) void assign_statement::accept(parser_visitor *visitor)
{ {
visitor->visit(this); visitor->visit(this);
} }
parser::parser(const std::vector<token>& tokens) assign_statement::assign_statement(const std::string& lvalue, std::unique_ptr<expression>&& rvalue)
: tokens(tokens.cbegin()), end(tokens.cend()) : 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(); return parse_block();
} }
const std::list<std::unique_ptr<error>>& parser::errors() const noexcept
{
return iterator.errors();
}
std::unique_ptr<expression> parser::parse_factor() std::unique_ptr<expression> parser::parse_factor()
{ {
if (tokens->of() == source::token::type::identifier) if (iterator->of() == source::token::type::identifier)
{ {
auto result = std::make_unique<variable_expression>(tokens->identifier()); auto result = std::make_unique<variable_expression>(iterator->identifier());
++tokens; ++iterator;
return result; 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<integer_literal>(tokens->number()); auto result = std::make_unique<integer_literal>(iterator->number());
++tokens; ++iterator;
return result; 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(); auto expression = parse_expression();
++tokens; ++iterator;
return expression; return expression;
} }
@ -220,14 +301,14 @@ namespace elna::source
std::unique_ptr<expression> parser::parse_term() std::unique_ptr<expression> parser::parse_term()
{ {
auto lhs = parse_factor(); 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; return lhs;
} }
while (tokens->of() == source::token::type::factor_operator) while (iterator->of() == source::token::type::factor_operator)
{ {
auto _operator = tokens->identifier()[0]; auto _operator = iterator->identifier()[0];
++tokens; ++iterator;
auto rhs = parse_factor(); auto rhs = parse_factor();
lhs = std::make_unique<binary_expression>(std::move(lhs), lhs = std::make_unique<binary_expression>(std::move(lhs),
@ -239,14 +320,14 @@ namespace elna::source
std::unique_ptr<expression> parser::parse_expression() std::unique_ptr<expression> parser::parse_expression()
{ {
auto term = parse_term(); 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; return term;
} }
while (tokens->of() == source::token::type::term_operator) while (iterator->of() == source::token::type::term_operator)
{ {
auto _operator = tokens->identifier()[0]; auto _operator = iterator->identifier()[0];
++tokens; ++iterator;
auto rhs = parse_term(); auto rhs = parse_term();
term = std::make_unique<binary_expression>(std::move(term), term = std::make_unique<binary_expression>(std::move(term),
@ -257,22 +338,22 @@ namespace elna::source
std::unique_ptr<definition> parser::parse_definition() std::unique_ptr<definition> parser::parse_definition()
{ {
auto definition_identifier = advance(token::type::identifier); auto definition_identifier = iterator.advance(token::type::identifier);
if (!definition_identifier.has_value()) if (!definition_identifier.has_value())
{ {
return nullptr; return nullptr;
} }
if (!skip(token::type::equals)) if (!iterator.skip(token::type::equals))
{ {
return nullptr; return nullptr;
} }
if (tokens->of() == source::token::type::number) if (iterator->of() == source::token::type::number)
{ {
auto result = std::make_unique<definition>(definition_identifier.value().get().identifier(), auto result = std::make_unique<definition>(definition_identifier.value().get().identifier(),
std::make_unique<integer_literal>(tokens->number())); std::make_unique<integer_literal>(iterator->number()));
++tokens; ++iterator;
return result; return result;
} }
return nullptr; return nullptr;
@ -280,7 +361,7 @@ namespace elna::source
std::unique_ptr<declaration> parser::parse_declaration() std::unique_ptr<declaration> parser::parse_declaration()
{ {
auto declaration_identifier = advance(token::type::identifier); auto declaration_identifier = iterator.advance(token::type::identifier);
if (!declaration_identifier.has_value()) if (!declaration_identifier.has_value())
{ {
@ -291,21 +372,25 @@ namespace elna::source
std::unique_ptr<statement> parser::parse_statement() std::unique_ptr<statement> 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(); return parse_bang_statement();
} }
else if (tokens->of() == source::token::type::begin) else if (iterator.current(token::type::begin))
{ {
return parse_compound_statement(); return parse_compound_statement();
} }
errors.push_back(std::make_unique<unexpected_token>(unexpected_token{ *tokens })); iterator.add_error(*iterator);
return nullptr; return nullptr;
} }
std::unique_ptr<bang_statement> parser::parse_bang_statement() std::unique_ptr<bang_statement> parser::parse_bang_statement()
{ {
if (!advance(token::type::bang)) if (!iterator.advance(token::type::bang))
{ {
return nullptr; return nullptr;
} }
@ -321,7 +406,7 @@ namespace elna::source
std::unique_ptr<compound_statement> parser::parse_compound_statement() std::unique_ptr<compound_statement> parser::parse_compound_statement()
{ {
if (!advance(token::type::begin)) if (!iterator.advance(token::type::begin))
{ {
return nullptr; return nullptr;
} }
@ -332,18 +417,18 @@ namespace elna::source
{ {
result->statements().push_back(std::move(next_statement)); 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; break;
} }
else else
{ {
errors.push_back(std::make_unique<unexpected_token>(*tokens)); iterator.add_error(*iterator);
break; break;
} }
} }
@ -351,33 +436,49 @@ namespace elna::source
return result; return result;
} }
std::unique_ptr<assign_statement> 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<assign_statement>(name.value().get().identifier(), std::move(rvalue));
}
std::vector<std::unique_ptr<definition>> parser::parse_definitions() std::vector<std::unique_ptr<definition>> parser::parse_definitions()
{ {
std::vector<std::unique_ptr<definition>> definitions; std::vector<std::unique_ptr<definition>> definitions;
if (tokens->of() != token::type::let) if (iterator->of() != token::type::let)
{ {
return definitions; return definitions;
} }
++tokens; // Skip const. ++iterator; // Skip const.
std::unique_ptr<definition> parsed_definition; std::unique_ptr<definition> parsed_definition;
while ((parsed_definition = parse_definition()) != nullptr) while ((parsed_definition = parse_definition()) != nullptr)
{ {
definitions.push_back(std::move(parsed_definition)); 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; break;
} }
else else
{ {
errors.push_back(std::make_unique<unexpected_token>(*tokens)); iterator.add_error(*iterator);
break; break;
} }
} }
@ -388,29 +489,29 @@ namespace elna::source
{ {
std::vector<std::unique_ptr<declaration>> declarations; std::vector<std::unique_ptr<declaration>> declarations;
if (tokens->of() != token::type::var) if (iterator->of() != token::type::var)
{ {
return declarations; return declarations;
} }
++tokens; // Skip var. ++iterator; // Skip var.
std::unique_ptr<declaration> parsed_declaration; std::unique_ptr<declaration> parsed_declaration;
while ((parsed_declaration = parse_declaration()) != nullptr) while ((parsed_declaration = parse_declaration()) != nullptr)
{ {
declarations.push_back(std::move(parsed_declaration)); 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; break;
} }
else else
{ {
errors.push_back(std::make_unique<unexpected_token>(*tokens)); iterator.add_error(*iterator);
break; break;
} }
} }
@ -430,25 +531,4 @@ namespace elna::source
return std::make_unique<block>(std::move(definitions), return std::make_unique<block>(std::move(definitions),
std::move(declarations), std::move(parsed_statement)); std::move(declarations), std::move(parsed_statement));
} }
std::optional<std::reference_wrapper<const token>> 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<unexpected_token>(*tokens));
return std::optional<std::reference_wrapper<const token>>();
}
bool parser::skip(const token::type token_type)
{
if (tokens->of() == token_type)
{
++tokens;
return true;
}
errors.push_back(std::make_unique<unexpected_token>(*tokens));
return false;
}
} }

View File

@ -16,4 +16,72 @@ namespace elna::source
{ {
return this->m_position.column; 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<info> 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<info> 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;
}
} }

38
source/semantic.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "elna/source/semantic.hpp"
#include <cstdlib>
namespace elna::source
{
void name_analysis_visitor::visit(definition *definition)
{
this->table->enter(definition->identifier(),
std::make_shared<constant_info>(constant_info(definition->body().number())));
}
void name_analysis_visitor::visit(declaration *declaration)
{
this->table->enter(declaration->identifier(),
std::make_shared<variable_info>(variable_info()));
}
void name_analysis_visitor::visit(block *block)
{
this->table = block->table();
empty_visitor::visit(block);
this->table->enter("main",
std::make_shared<procedure_info>());
}
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<procedure_info>(block->table()->lookup("main"))
->stack_size(std::abs(this->offset));
}
}

View File

@ -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<info> 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<info> 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)
{
}
}

View File

@ -1,5 +1,5 @@
var x; var x;
begin begin
x := 5; x := 5;
! 5 ! x
end. end.

View File

@ -1,9 +1,11 @@
#include <boost/process.hpp>
#include <filesystem>
#include <iostream>
#include "elna/tester.hpp" #include "elna/tester.hpp"
#include <future>
#include <iostream>
#include <boost/process.hpp>
#include <boost/asio.hpp>
namespace elna namespace elna
{ {
std::uint32_t test_results::total() const noexcept std::uint32_t test_results::total() const noexcept
@ -26,10 +28,10 @@ namespace elna
return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE; 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; ++m_total;
if (exit_code == 0) if (status == test_status::successful)
{ {
++m_passed; ++m_passed;
} }
@ -45,7 +47,7 @@ namespace elna
return in_build_directory() / path; 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(); const std::filesystem::path test_filename = test_entry.path().filename();
@ -61,7 +63,7 @@ namespace elna
"-o", test_object.string(), test_entry.path().string()); "-o", test_object.string(), test_entry.path().string());
if (status != 0) if (status != 0)
{ {
return status; return test_result{ test_status::compile_failed, status };
} }
status = boost::process::system( status = boost::process::system(
"/opt/riscv/bin/riscv32-unknown-elf-ld", "/opt/riscv/bin/riscv32-unknown-elf-ld",
@ -74,37 +76,55 @@ namespace elna
"--start-group", "-lgcc", "-lc", "-lgloss", "--end-group", "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group",
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o" "/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(); const std::filesystem::path test_filename = test_entry.path().filename();
std::filesystem::path test_binary = in_build_directory(test_filename); std::filesystem::path test_binary = in_build_directory(test_filename);
test_binary.replace_extension(); test_binary.replace_extension();
std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; boost::asio::io_service io_service;
expectation_path.replace_extension(".txt"); std::future<std::string> buffer;
boost::process::child spike(
std::cout << "Running " << test_binary << std::endl;
boost::process::pipe pipe_stream;
boost::process::child vm(
"/opt/riscv/bin/spike", "--isa=RV32IMAC", "/opt/riscv/bin/spike", "--isa=RV32IMAC",
"/opt/riscv/riscv32-unknown-elf/bin/pk", "/opt/riscv/riscv32-unknown-elf/bin/pk",
test_binary.string(), 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( io_service.run();
"/usr/bin/diff", "-Nur", "--color",
expectation_path.string(), "-",
boost::process::std_in < pipe_stream
);
vm.wait();
diff.wait();
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) static test_results run_in_path(const std::filesystem::path test_directory)
@ -117,26 +137,37 @@ namespace elna
{ {
continue; continue;
} }
int status{ 0 }; test_result result;
std::cout << "Running " << test_entry << std::endl;
try 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) catch (const boost::process::process_error& exception)
{ {
std::cout << exception.what() << std::endl; result.status = test_status::build_failed;
status = 3; result.output = exception.what();
continue; std::cout << result.output << std::endl;
} }
if (status == 0) print_result(test_entry, result);
{ results.add_exit_code(result.status);
status = run_test(test_entry);
}
results.add_exit_code(status);
} }
return results; 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() int main()