From f2a20c28257f930eb6c8da3227932fe239ad5987 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 23 Mar 2024 14:53:26 +0100 Subject: [PATCH] Support failure tests --- TODO | 2 + cli/cl.cpp | 9 +- include/elna/source/lexer.hpp | 2 + include/elna/source/parser.hpp | 4 +- include/elna/source/result.hpp | 5 + include/elna/source/semantic.hpp | 5 + include/elna/tester.hpp | 9 ++ source/lexer.cpp | 57 +++++++++- source/parser.cpp | 75 +++++++++++-- tests/expectations/procedure_definition.txt | 1 + tests/failures/single_word_error.txt | 1 + tests/procedure_definition.eln | 6 + tests/single_word_error.eln | 1 + tests/tester.cpp | 117 +++++++++++++------- 14 files changed, 241 insertions(+), 53 deletions(-) create mode 100644 tests/expectations/procedure_definition.txt create mode 100644 tests/failures/single_word_error.txt create mode 100644 tests/procedure_definition.eln create mode 100644 tests/single_word_error.eln diff --git a/TODO b/TODO index 1b08cbb..343a43b 100644 --- a/TODO +++ b/TODO @@ -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. diff --git a/cli/cl.cpp b/cli/cl.cpp index 1c8b49c..e4e9c7d 100644 --- a/cli/cl.cpp +++ b/cli/cl.cpp @@ -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); diff --git a/include/elna/source/lexer.hpp b/include/elna/source/lexer.hpp index 0e3ca3e..0c772e9 100644 --- a/include/elna/source/lexer.hpp +++ b/include/elna/source/lexer.hpp @@ -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{}; diff --git a/include/elna/source/parser.hpp b/include/elna/source/parser.hpp index b3aef1e..291aa8a 100644 --- a/include/elna/source/parser.hpp +++ b/include/elna/source/parser.hpp @@ -279,6 +279,7 @@ namespace elna::source std::unique_ptr parse_term(); std::unique_ptr parse_expression(); std::unique_ptr parse_constant_definition(); + std::unique_ptr parse_procedure_definition(); std::unique_ptr parse_declaration(); std::unique_ptr parse_statement(); std::unique_ptr parse_call_statement(); @@ -286,7 +287,8 @@ namespace elna::source std::unique_ptr parse_assign_statement(); std::unique_ptr parse_if_statement(); std::unique_ptr parse_while_statement(); - std::vector> parse_definitions(); + std::vector> parse_constant_definitions(); + std::vector> parse_procedure_definitions(); std::vector> parse_declarations(); std::unique_ptr parse_block(); diff --git a/include/elna/source/result.hpp b/include/elna/source/result.hpp index b19feb9..15f79c6 100644 --- a/include/elna/source/result.hpp +++ b/include/elna/source/result.hpp @@ -70,6 +70,11 @@ namespace elna::source errors().emplace_back(std::make_unique(first)); } + explicit result(E&& errors) + : payload(std::move(errors)) + { + } + bool has_errors() const noexcept { return std::holds_alternative(payload); diff --git a/include/elna/source/semantic.hpp b/include/elna/source/semantic.hpp index 5022814..dd4c2ae 100644 --- a/include/elna/source/semantic.hpp +++ b/include/elna/source/semantic.hpp @@ -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: + }; } diff --git a/include/elna/tester.hpp b/include/elna/tester.hpp index 1690a33..e031ac6 100644 --- a/include/elna/tester.hpp +++ b/include/elna/tester.hpp @@ -6,6 +6,10 @@ #define BOOST_PROCESS_USE_STD_FS +#include +#include +#include + 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 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); } diff --git a/source/lexer.cpp b/source/lexer.cpp index 1382259..bfd5683 100644 --- a/source/lexer.cpp +++ b/source/lexer.cpp @@ -1,4 +1,5 @@ #include "elna/source/lexer.hpp" +#include #include #include @@ -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&& tokens, const position last_position) diff --git a/source/parser.cpp b/source/parser.cpp index e0dc3c3..6d1148b 100644 --- a/source/parser.cpp +++ b/source/parser.cpp @@ -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 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> definitions(constants.size() + procedures.size()); + std::vector>::iterator definition = definitions.begin(); + + for (auto& constant : constants) + { + *definition++ = std::move(constant); + } + for (auto& procedure : procedures) + { + *definition++ = std::move(procedure); + } return std::make_unique(std::move(definitions), std::move(declarations), std::move(parsed_statement)); } @@ -492,6 +501,28 @@ namespace elna::source return nullptr; } + std::unique_ptr 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(definition_identifier->get().identifier(), + std::move(definition_body)); + } + std::unique_ptr parser::parse_declaration() { auto declaration_identifier = iterator.advance(token::type::identifier); @@ -641,9 +672,9 @@ namespace elna::source return std::make_unique(std::move(condition), std::move(body)); } - std::vector> parser::parse_definitions() + std::vector> parser::parse_constant_definitions() { - std::vector> definitions; + std::vector> definitions; if (iterator->of() != token::type::let) { @@ -651,7 +682,7 @@ namespace elna::source } ++iterator; // Skip const. - std::unique_ptr parsed_definition; + std::unique_ptr 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> parser::parse_procedure_definitions() + { + std::vector> 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> parser::parse_declarations() { std::vector> declarations; @@ -709,7 +757,7 @@ namespace elna::source std::unique_ptr 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> definitions(constants.size()); + std::vector>::iterator definition = definitions.begin(); + + for (auto& constant : constants) + { + *definition++ = std::move(constant); + } return std::make_unique(std::move(definitions), std::move(declarations), std::move(parsed_statement)); } diff --git a/tests/expectations/procedure_definition.txt b/tests/expectations/procedure_definition.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/tests/expectations/procedure_definition.txt @@ -0,0 +1 @@ +2 diff --git a/tests/failures/single_word_error.txt b/tests/failures/single_word_error.txt new file mode 100644 index 0000000..8cdf363 --- /dev/null +++ b/tests/failures/single_word_error.txt @@ -0,0 +1 @@ +1:1: Unexpected token «identifier» diff --git a/tests/procedure_definition.eln b/tests/procedure_definition.eln new file mode 100644 index 0000000..f862f9a --- /dev/null +++ b/tests/procedure_definition.eln @@ -0,0 +1,6 @@ +proc f; + writei(5); + +begin + writei(2) +end. diff --git a/tests/single_word_error.eln b/tests/single_word_error.eln new file mode 100644 index 0000000..8bd6648 --- /dev/null +++ b/tests/single_word_error.eln @@ -0,0 +1 @@ +asdf diff --git a/tests/tester.cpp b/tests/tester.cpp index 9538da4..6376aaa 100755 --- a/tests/tester.cpp +++ b/tests/tester.cpp @@ -1,11 +1,7 @@ #include "elna/tester.hpp" -#include #include -#include -#include - 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 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(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,37 +84,57 @@ 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", - "-o", test_binary.string(), - "-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/", - "-L/opt/riscv/riscv32-unknown-elf/lib", - "/opt/riscv/riscv32-unknown-elf/lib/crt0.o", - "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o", - test_object.string(), - "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group", - "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o" - ); + 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", + "/opt/riscv/riscv32-unknown-elf/lib/crt0.o", + "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o", + 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 buffer; - boost::process::child qemu( - 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() }; + return run_for_output(context, + boost::process::search_path("qemu-riscv32"), + { 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); }