Support failure tests

This commit is contained in:
Eugen Wissner 2024-03-23 14:53:26 +01:00
parent 7e5f7f492d
commit f2a20c2825
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
14 changed files with 241 additions and 53 deletions

2
TODO
View File

@ -14,6 +14,8 @@
- Support immediates greater than 12 bits.
- It seems instructions are correctly encoded only if the compiler is running
on a little endian architecture.
- Print filename in the error message.
- Merge declaration and definition nodes.
# Shell
- Persist the history.

View File

@ -43,12 +43,19 @@ namespace elna::cli
}
return 1;
}
auto ast = source::parser(std::move(lex_result.success())).parse();
source::parser parser{ std::move(lex_result.success()) };
auto ast = parser.parse();
if (ast == nullptr)
{
for (const auto& compile_error : parser.errors())
{
std::cout << compile_error->line() << ':' << compile_error->column()
<< ": " << compile_error->what() << std::endl;
}
return 2;
}
source::name_analysis_visitor().visit(ast.get());
source::type_analysis_visitor().visit(ast.get());
source::allocator_visitor().visit(ast.get());
riscv::riscv32_elf(ast.get(), out_file);

View File

@ -107,6 +107,8 @@ namespace elna::source
std::int32_t number() const;
const elna::source::position& position() const noexcept;
std::string to_string() const;
private:
type m_type;
value m_value{};

View File

@ -279,6 +279,7 @@ namespace elna::source
std::unique_ptr<expression> parse_term();
std::unique_ptr<expression> parse_expression();
std::unique_ptr<constant_definition> parse_constant_definition();
std::unique_ptr<procedure_definition> parse_procedure_definition();
std::unique_ptr<declaration> parse_declaration();
std::unique_ptr<statement> parse_statement();
std::unique_ptr<call_statement> parse_call_statement();
@ -286,7 +287,8 @@ namespace elna::source
std::unique_ptr<assign_statement> parse_assign_statement();
std::unique_ptr<if_statement> parse_if_statement();
std::unique_ptr<while_statement> parse_while_statement();
std::vector<std::unique_ptr<definition>> parse_definitions();
std::vector<std::unique_ptr<constant_definition>> parse_constant_definitions();
std::vector<std::unique_ptr<procedure_definition>> parse_procedure_definitions();
std::vector<std::unique_ptr<declaration>> parse_declarations();
std::unique_ptr<block> parse_block();

View File

@ -70,6 +70,11 @@ namespace elna::source
errors().emplace_back(std::make_unique<U>(first));
}
explicit result(E&& errors)
: payload(std::move(errors))
{
}
bool has_errors() const noexcept
{
return std::holds_alternative<E>(payload);

View File

@ -22,4 +22,9 @@ namespace elna::source
void visit(declaration *declaration) override;
void visit(block *block) override;
};
class type_analysis_visitor final : public empty_visitor
{
public:
};
}

View File

@ -6,6 +6,10 @@
#define BOOST_PROCESS_USE_STD_FS
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <boost/process/v2.hpp>
namespace elna
{
enum class test_status
@ -14,6 +18,7 @@ namespace elna
compile_failed,
build_failed,
expectation_failed,
expectation_not_found
};
struct test_result final
@ -39,5 +44,9 @@ namespace elna
void add_exit_code(const test_status result) noexcept;
};
test_result run_for_output(boost::asio::io_context& context, const std::filesystem::path& binary,
std::initializer_list<boost::string_view> arguments);
test_result build_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry);
test_result run_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry);
void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result);
}

View File

@ -1,4 +1,5 @@
#include "elna/source/lexer.hpp"
#include <cassert>
#include <variant>
#include <sstream>
@ -212,6 +213,60 @@ namespace elna::source
|| of() == type::boolean;
}
std::string token::to_string() const
{
switch (this->m_type)
{
case type::number:
return "«number»";
case type::boolean:
return "«boolean»";
case type::term_operator:
return "«term_operator»";
case type::let:
return "«const»";
case type::identifier:
return "«identifier»";
case type::equals:
return "«=»";
case type::var:
return "«var»";
case type::semicolon:
return "«;»";
case type::left_paren:
return "«(»";
case type::right_paren:
return "«)»";
case type::dot:
return "«)»";
case type::comma:
return "«,»";
case type::factor_operator:
return "«*»";
case type::eof:
return "«EOF»";
case type::begin:
return "«begin»";
case type::end:
return "«end»";
case type::assignment:
return "«:=»";
case type::colon:
return "«:»";
case type::when:
return "«if»";
case type::then:
return "«then»";
case type::_while:
return "«while»";
case type::_do:
return "«do»";
case type::procedure:
return "«proc»";
};
assert(false);
}
unexpected_character::unexpected_character(const std::string& character, const source::position position)
: error(position), character(character)
{
@ -233,7 +288,7 @@ namespace elna::source
std::string unexpected_token::what() const
{
return "Unexpected token";
return "Unexpected token " + m_token.to_string();
}
lexer::lexer(std::vector<token>&& tokens, const position last_position)

View File

@ -49,9 +49,9 @@ namespace elna::source
void empty_visitor::visit(block *block)
{
for (const auto& block_definition : block->definitions())
for (const auto& constant : block->definitions())
{
block_definition->accept(this);
constant->accept(this);
}
for (const auto& block_declaration : block->declarations())
{
@ -83,9 +83,6 @@ namespace elna::source
{
}
/**
* AST node.
*/
void node::accept(parser_visitor *)
{
}
@ -381,14 +378,26 @@ namespace elna::source
std::unique_ptr<program> parser::parse()
{
auto definitions = parse_definitions();
auto constants = parse_constant_definitions();
auto declarations = parse_declarations();
auto procedures = parse_procedure_definitions();
auto parsed_statement = parse_statement();
if (parsed_statement == nullptr)
{
return nullptr;
}
std::vector<std::unique_ptr<definition>> definitions(constants.size() + procedures.size());
std::vector<std::unique_ptr<definition>>::iterator definition = definitions.begin();
for (auto& constant : constants)
{
*definition++ = std::move(constant);
}
for (auto& procedure : procedures)
{
*definition++ = std::move(procedure);
}
return std::make_unique<program>(std::move(definitions),
std::move(declarations), std::move(parsed_statement));
}
@ -492,6 +501,28 @@ namespace elna::source
return nullptr;
}
std::unique_ptr<procedure_definition> parser::parse_procedure_definition()
{
if (!iterator.skip(token::type::procedure))
{
return nullptr;
}
auto definition_identifier = iterator.advance(token::type::identifier);
if (!definition_identifier.has_value() || !iterator.skip(token::type::semicolon))
{
return nullptr;
}
auto definition_body = parse_block();
if (definition_body == nullptr || !iterator.skip(token::type::semicolon))
{
return nullptr;
}
return std::make_unique<procedure_definition>(definition_identifier->get().identifier(),
std::move(definition_body));
}
std::unique_ptr<declaration> parser::parse_declaration()
{
auto declaration_identifier = iterator.advance(token::type::identifier);
@ -641,9 +672,9 @@ namespace elna::source
return std::make_unique<while_statement>(std::move(condition), std::move(body));
}
std::vector<std::unique_ptr<definition>> parser::parse_definitions()
std::vector<std::unique_ptr<constant_definition>> parser::parse_constant_definitions()
{
std::vector<std::unique_ptr<definition>> definitions;
std::vector<std::unique_ptr<constant_definition>> definitions;
if (iterator->of() != token::type::let)
{
@ -651,7 +682,7 @@ namespace elna::source
}
++iterator; // Skip const.
std::unique_ptr<definition> parsed_definition;
std::unique_ptr<constant_definition> parsed_definition;
while ((parsed_definition = parse_constant_definition()) != nullptr)
{
definitions.push_back(std::move(parsed_definition));
@ -674,6 +705,23 @@ namespace elna::source
return definitions;
}
std::vector<std::unique_ptr<procedure_definition>> parser::parse_procedure_definitions()
{
std::vector<std::unique_ptr<procedure_definition>> definitions;
while (iterator.current(token::type::procedure))
{
auto parsed_definition = parse_procedure_definition();
if (parsed_definition == nullptr)
{
break;
}
definitions.push_back(std::move(parsed_definition));
}
return definitions;
}
std::vector<std::unique_ptr<declaration>> parser::parse_declarations()
{
std::vector<std::unique_ptr<declaration>> declarations;
@ -709,7 +757,7 @@ namespace elna::source
std::unique_ptr<block> parser::parse_block()
{
auto definitions = parse_definitions();
auto constants = parse_constant_definitions();
auto declarations = parse_declarations();
auto parsed_statement = parse_statement();
@ -717,6 +765,13 @@ namespace elna::source
{
return nullptr;
}
std::vector<std::unique_ptr<definition>> definitions(constants.size());
std::vector<std::unique_ptr<definition>>::iterator definition = definitions.begin();
for (auto& constant : constants)
{
*definition++ = std::move(constant);
}
return std::make_unique<block>(std::move(definitions),
std::move(declarations), std::move(parsed_statement));
}

View File

@ -0,0 +1 @@
2

View File

@ -0,0 +1 @@
1:1: Unexpected token «identifier»

View File

@ -0,0 +1,6 @@
proc f;
writei(5);
begin
writei(2)
end.

View File

@ -0,0 +1 @@
asdf

View File

@ -1,11 +1,7 @@
#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
@ -47,7 +43,36 @@ namespace elna
return in_build_directory() / path;
}
static test_result build_test(const std::filesystem::directory_entry& test_entry)
test_result run_for_output(boost::asio::io_context& context, const std::filesystem::path& binary,
std::initializer_list<boost::string_view> arguments)
{
boost::asio::readable_pipe read_pipe{ context };
test_result result{};
std::string output;
boost::asio::dynamic_string_buffer buffer = boost::asio::dynamic_buffer(output);
boost::system::error_code ec;
boost::process::v2::process elna_child(context,
binary, arguments,
boost::process::v2::process_stdio{ nullptr, read_pipe, read_pipe });
do
{
std::size_t transferred = read_pipe.read_some(buffer.prepare(512), ec);
if (transferred == 0)
{
break;
}
buffer.commit(transferred);
result.output.append(boost::asio::buffer_cast<const char *>(buffer.data()), buffer.size());
buffer.consume(transferred);
}
while (ec == boost::system::errc::success);
result.code = elna_child.wait();
return result;
}
test_result build_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry)
{
const std::filesystem::path test_filename = test_entry.path().filename();
@ -59,14 +84,17 @@ namespace elna
std::filesystem::remove(test_binary);
std::filesystem::remove(test_object);
auto status = boost::process::system("./build/bin/elna",
"-o", test_object.string(), test_entry.path().string());
if (status != 0)
test_result result = run_for_output(context, "./build/bin/elna",
{ "-o", test_object.string(), test_entry.path().string() });
if (result.code != 0)
{
return test_result{ test_status::compile_failed, status };
result.status = test_status::compile_failed;
return result;
}
status = boost::process::system(
"/opt/riscv/bin/riscv32-unknown-elf-ld",
boost::process::v2::execute(boost::process::v2::process(
context, "/opt/riscv/bin/riscv32-unknown-elf-ld",
{
"-o", test_binary.string(),
"-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/",
"-L/opt/riscv/riscv32-unknown-elf/lib",
@ -75,21 +103,38 @@ namespace elna
test_object.string(),
"--start-group", "-lgcc", "-lc", "-lgloss", "--end-group",
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o"
);
}
));
return test_result{};
}
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 test_filename = test_entry.path().filename();
test_filename.replace_extension(".txt");
std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename;
expectation_path.replace_extension(".txt");
const std::filesystem::path expectation_path =
test_entry.path().parent_path() / "expectations" / test_filename;
const std::filesystem::path failures_path =
test_entry.path().parent_path() / "failures" / test_filename;
std::string expected_result_path;
if (std::filesystem::exists(expectation_path))
{
expected_result_path = expectation_path.string();
}
else if (std::filesystem::exists(failures_path))
{
expected_result_path = failures_path.string();
}
else
{
return test_result{ test_status::expectation_not_found, run.code, run.output };
}
boost::process::opstream pipe_stream;
boost::process::child diff(
boost::process::search_path("diff"), "-Nur", "--color",
expectation_path.string(), "-",
expected_result_path, "-",
boost::process::std_in < pipe_stream
);
@ -100,35 +145,27 @@ namespace elna
if (diff.exit_code() == 0)
{
return run;
return test_result{ test_status::successful, run.code, run.output };
}
return test_result{ test_status::expectation_failed, run.code, run.output };
}
static test_result run_test(const std::filesystem::directory_entry& test_entry)
test_result run_test(boost::asio::io_context& context, 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();
boost::asio::io_service io_service;
std::future<std::string> buffer;
boost::process::child qemu(
return run_for_output(context,
boost::process::search_path("qemu-riscv32"),
test_binary.string(),
boost::process::std_out > buffer,
boost::process::std_err > buffer,
io_service
);
io_service.run();
return test_result{ test_status::successful, qemu.exit_code(), buffer.get() };
{ test_binary.string() });
}
static test_results run_in_path(const std::filesystem::path test_directory)
{
test_results results;
boost::asio::io_context context;
for (const auto& test_entry : std::filesystem::directory_iterator(test_directory))
{
@ -141,10 +178,10 @@ namespace elna
std::cout << "Running " << test_entry << std::endl;
try
{
result = build_test(test_entry);
result = build_test(context, test_entry);
if (result.status == test_status::successful)
{
result = run_test(test_entry);
result = run_test(context, test_entry);
}
result = check_expectation(test_entry, result);
}