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/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
10
TODO
@ -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.
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
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
|
#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);
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
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;
|
var x;
|
||||||
begin
|
begin
|
||||||
x := 5;
|
x := 5;
|
||||||
! 5
|
! x
|
||||||
end.
|
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 "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()
|
||||||
|
Loading…
Reference in New Issue
Block a user