Assign variables
This commit is contained in:
parent
42d2038c4d
commit
8240443cd1
@ -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
10
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.
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
25
include/elna/source/semantic.hpp
Normal file
25
include/elna/source/semantic.hpp
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
38
source/semantic.cpp
Normal 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));
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
var x;
|
||||
begin
|
||||
x := 5;
|
||||
! 5
|
||||
! x
|
||||
end.
|
||||
|
103
tests/tester.cpp
103
tests/tester.cpp
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user