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/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

10
TODO
View File

@ -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.

View File

@ -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<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[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)
{
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(
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)
{

View File

@ -1,5 +1,6 @@
#include "elna/cli/cl.hpp"
#include "elna/backend/target.hpp"
#include "elna/source/semantic.hpp"
#include <cstddef>
#include <fstream>
#include <sstream>
@ -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;

View File

@ -1,7 +1,6 @@
#pragma once
#include <cstdint>
#include <unordered_map>
#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<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::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;

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "elna/source/result.hpp"
@ -128,11 +129,93 @@ namespace elna::source
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.
* \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
#include <boost/core/noncopyable.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <elna/source/lexer.hpp>
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<std::unique_ptr<statement>>& statements();
};
class assignment_statement : public statement
class assign_statement : public statement
{
std::unique_ptr<variable_expression> lvalue;
std::unique_ptr<expression> rvalue;
std::string m_lvalue;
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;
std::string& lvalue() noexcept;
expression& rvalue();
};
/**
@ -124,6 +141,7 @@ namespace elna::source
std::unique_ptr<statement> m_body;
std::vector<std::unique_ptr<definition>> m_definitions;
std::vector<std::unique_ptr<declaration>> m_declarations;
std::shared_ptr<symbol_table> m_table;
public:
block(std::vector<std::unique_ptr<definition>>&& definitions,
@ -134,6 +152,7 @@ namespace elna::source
statement& body();
std::vector<std::unique_ptr<definition>>& definitions() noexcept;
std::vector<std::unique_ptr<declaration>>& declarations() noexcept;
std::shared_ptr<symbol_table> 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<token>& tokens);
std::unique_ptr<block> parse();
private:
std::unique_ptr<expression> parse_factor();
std::unique_ptr<expression> parse_term();
std::unique_ptr<expression> parse_expression();
@ -189,15 +203,28 @@ namespace elna::source
std::unique_ptr<statement> parse_statement();
std::unique_ptr<bang_statement> parse_bang_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<declaration>> parse_declarations();
std::unique_ptr<block> parse_block();
std::optional<std::reference_wrapper<const token>> advance(const token::type token_type);
bool skip(const token::type token_type);
lexer iterator;
std::vector<token>::const_iterator tokens;
std::vector<token>::const_iterator end;
std::list<std::unique_ptr<error>> errors;
public:
parser(lexer&& tokens);
/**
* 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
#include <cstddef>
#include <cstdint>
#include <variant>
#include <list>
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
namespace elna::source
{
@ -50,8 +53,8 @@ namespace elna::source
using E = std::list<std::unique_ptr<source::error>>;
template<typename... Args, typename = std::enable_if<std::is_constructible_v<T, Args...>>>
explicit result(Args&&... arguments)
: payload(std::forward<Args>(arguments)...)
explicit result(std::in_place_t, 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>>>
explicit result(U&& first)
: payload(E())
{
errors().emplace_back(std::make_unique<U>(first));
}
@ -89,4 +93,75 @@ namespace elna::source
private:
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
#include <cstdint>
#include <string>
#include <filesystem>
#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);
}

View File

@ -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::vector<token>>;
std::pair<text_iterator, text_iterator> 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<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;
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<lexer>(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<lexer>(std::in_place, std::move(tokens), iterator.position());
}
}

View File

@ -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<std::unique_ptr<definition>>&& definitions,
std::vector<std::unique_ptr<declaration>>&& declarations,
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;
}
std::shared_ptr<symbol_table> 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<token>& tokens)
: tokens(tokens.cbegin()), end(tokens.cend())
assign_statement::assign_statement(const std::string& lvalue, std::unique_ptr<expression>&& 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<std::unique_ptr<error>>& parser::errors() const noexcept
{
return iterator.errors();
}
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());
++tokens;
auto result = std::make_unique<variable_expression>(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<integer_literal>(tokens->number());
++tokens;
auto result = std::make_unique<integer_literal>(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<expression> 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<binary_expression>(std::move(lhs),
@ -239,14 +320,14 @@ namespace elna::source
std::unique_ptr<expression> 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<binary_expression>(std::move(term),
@ -257,22 +338,22 @@ namespace elna::source
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())
{
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>(definition_identifier.value().get().identifier(),
std::make_unique<integer_literal>(tokens->number()));
++tokens;
std::make_unique<integer_literal>(iterator->number()));
++iterator;
return result;
}
return nullptr;
@ -280,7 +361,7 @@ namespace elna::source
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())
{
@ -291,21 +372,25 @@ namespace elna::source
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();
}
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>(unexpected_token{ *tokens }));
iterator.add_error(*iterator);
return nullptr;
}
std::unique_ptr<bang_statement> 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<compound_statement> 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<unexpected_token>(*tokens));
iterator.add_error(*iterator);
break;
}
}
@ -351,33 +436,49 @@ namespace elna::source
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>> definitions;
if (tokens->of() != token::type::let)
if (iterator->of() != token::type::let)
{
return definitions;
}
++tokens; // Skip const.
++iterator; // Skip const.
std::unique_ptr<definition> 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<unexpected_token>(*tokens));
iterator.add_error(*iterator);
break;
}
}
@ -388,29 +489,29 @@ namespace elna::source
{
std::vector<std::unique_ptr<declaration>> declarations;
if (tokens->of() != token::type::var)
if (iterator->of() != token::type::var)
{
return declarations;
}
++tokens; // Skip var.
++iterator; // Skip var.
std::unique_ptr<declaration> 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<unexpected_token>(*tokens));
iterator.add_error(*iterator);
break;
}
}
@ -430,25 +531,4 @@ namespace elna::source
return std::make_unique<block>(std::move(definitions),
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;
}
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;
begin
x := 5;
! 5
! x
end.

View File

@ -1,9 +1,11 @@
#include <boost/process.hpp>
#include <filesystem>
#include <iostream>
#include "elna/tester.hpp"
#include <future>
#include <iostream>
#include <boost/process.hpp>
#include <boost/asio.hpp>
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<std::string> 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()