From 7845c700d8e4b03085fcb64fc6c6678e91e5c24c Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 28 May 2024 23:39:04 +0200 Subject: [PATCH] Type check binary expressions --- TODO | 2 + backend/target.cpp | 3 -- include/elna/source/result.hpp | 4 +- include/elna/source/semantic.hpp | 1 + include/elna/source/types.hpp | 15 +++++++ package.json | 3 +- source/lexer.cpp | 4 ++ source/semantic.cpp | 50 ++++++++++++++++++++++++ source/types.cpp | 67 ++++++++++++++++++++++++++++++++ tools/tester.js | 20 +++++++--- 10 files changed, 158 insertions(+), 11 deletions(-) diff --git a/TODO b/TODO index b35e3f4..3c102fc 100644 --- a/TODO +++ b/TODO @@ -13,3 +13,5 @@ - Error message with an empty file wrongly says that a ")" is expected. - Support any expressions for constants. - Name analysis should fail if there are undefined symbols. +- type_mismatch error should get second type argument for the + expected type. diff --git a/backend/target.cpp b/backend/target.cpp index 5ab5d04..aa105c1 100644 --- a/backend/target.cpp +++ b/backend/target.cpp @@ -199,14 +199,11 @@ namespace elna::riscv ELFIO::relocation_section_accessor rela(writer, rel_sec); auto _writer = std::make_shared(text_sec, ro_sec, syma, stra); - // visitor _visitor{ _writer, table }; - // _visitor.visit(ast); auto references = generate(intermediate_code_generator, table, _writer); syma.arrange_local_symbols(); for (auto& reference : references) - // for (auto& reference : _visitor.references) { ELFIO::Elf_Word relocated_symbol = lookup(syma, reference.name); diff --git a/include/elna/source/result.hpp b/include/elna/source/result.hpp index acbc2cd..c219e50 100644 --- a/include/elna/source/result.hpp +++ b/include/elna/source/result.hpp @@ -134,7 +134,9 @@ namespace elna::source enum class operation { dereference, - argument + argument, + arithmetic, + comparison }; /** diff --git a/include/elna/source/semantic.hpp b/include/elna/source/semantic.hpp index 94ea5dc..afde1da 100644 --- a/include/elna/source/semantic.hpp +++ b/include/elna/source/semantic.hpp @@ -82,6 +82,7 @@ namespace elna::source void visit(integer_literal *literal) override; void visit(boolean_literal *literal) override; void visit(unary_expression *expression) override; + void visit(binary_expression *expression) override; void visit(call_statement *statement) override; void visit(constant_definition *definition) override; }; diff --git a/include/elna/source/types.hpp b/include/elna/source/types.hpp index 64bc3a3..917ddda 100644 --- a/include/elna/source/types.hpp +++ b/include/elna/source/types.hpp @@ -26,6 +26,9 @@ namespace elna::source * \return The type size in bytes. */ virtual std::size_t size() const noexcept; + + friend bool operator==(const type& lhs, const type& rhs) noexcept; + friend bool operator!=(const type& lhs, const type& rhs) noexcept; }; /** @@ -43,6 +46,9 @@ namespace elna::source * \param byte_size The type size in bytes. */ primitive_type(const std::string& type_name, const std::size_t byte_size); + + bool operator==(const primitive_type& that) const noexcept; + bool operator!=(const primitive_type& that) const noexcept; }; /** @@ -60,6 +66,9 @@ namespace elna::source * \param byte_size The type size in bytes. */ pointer_type(std::shared_ptr base_type, const std::size_t byte_size); + + bool operator==(const pointer_type& that) const noexcept; + bool operator!=(const pointer_type& that) const noexcept; }; /** @@ -77,8 +86,14 @@ namespace elna::source * \param byte_size Function pointer size. */ procedure_type(std::vector> arguments, const std::size_t byte_size); + + bool operator==(const procedure_type& that) const noexcept; + bool operator!=(const procedure_type& that) const noexcept; }; + bool operator==(const type& lhs, const type& rhs) noexcept; + bool operator!=(const type& lhs, const type& rhs) noexcept; + inline const primitive_type boolean_type{ "Boolean", 1 }; inline const primitive_type int_type{ "Int", 4 }; } diff --git a/package.json b/package.json index 4bb492e..ce54348 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "tools/index.js", "type": "module", "scripts": { - "cross": "node tools/cross.js" + "start": "node tools/cross.js", + "test": "node tools/tester.js" }, "author": "Eugen Wissner ", "license": "MPL-2.0", diff --git a/source/lexer.cpp b/source/lexer.cpp index cc414de..5a6ff71 100644 --- a/source/lexer.cpp +++ b/source/lexer.cpp @@ -216,6 +216,10 @@ namespace elna::source return "«proc»"; case type::comparison_operator: return "«comparison_operator»"; + case type::hat: + return "«^»"; + case type::at: + return "«@»"; }; assert(false); } diff --git a/source/semantic.cpp b/source/semantic.cpp index ec1e383..45c04be 100644 --- a/source/semantic.cpp +++ b/source/semantic.cpp @@ -196,6 +196,56 @@ namespace elna::source } } + void type_analysis_visitor::visit(binary_expression *expression) + { + empty_visitor::visit(expression); + + switch (expression->operation()) + { + case binary_operator::sum: + case binary_operator::subtraction: + case binary_operator::multiplication: + case binary_operator::division: + case binary_operator::less: + case binary_operator::greater: + case binary_operator::less_equal: + case binary_operator::greater_equal: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + auto lhs_type = std::dynamic_pointer_cast(expression->lhs().data_type); + auto rhs_type = std::dynamic_pointer_cast(expression->rhs().data_type); + + std::unique_ptr new_error; + if (lhs_type == nullptr || *lhs_type != int_type) + { + new_error = std::make_unique(lhs_type, + type_mismatch::operation::arithmetic, this->filename, expression->lhs().position()); + } + if (rhs_type == nullptr || *rhs_type != int_type) + { + new_error = std::make_unique(rhs_type, + type_mismatch::operation::arithmetic, this->filename, expression->rhs().position()); + } + if (new_error != nullptr) + { + m_errors.push_back(std::move(new_error)); + } + } + break; + case binary_operator::equals: + case binary_operator::not_equals: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + if (expression->lhs().data_type != expression->rhs().data_type) + { + auto new_error = std::make_unique(expression->rhs().data_type, + type_mismatch::operation::comparison, this->filename, expression->rhs().position()); + } + } + break; + } + } + void type_analysis_visitor::visit(call_statement *statement) { auto call_info = std::dynamic_pointer_cast(this->table->lookup(statement->name())); diff --git a/source/types.cpp b/source/types.cpp index 6e86771..dbc96a9 100644 --- a/source/types.cpp +++ b/source/types.cpp @@ -17,13 +17,80 @@ namespace elna::source { } + bool primitive_type::operator==(const primitive_type& that) const noexcept + { + return this->type_name == that.type_name; + } + + bool primitive_type::operator!=(const primitive_type& that) const noexcept + { + return this->type_name != that.type_name; + } + pointer_type::pointer_type(std::shared_ptr base_type, const std::size_t byte_size) : type(byte_size), base_type(base_type) { } + bool pointer_type::operator==(const pointer_type& that) const noexcept + { + return this->base_type == that.base_type; + } + + bool pointer_type::operator!=(const pointer_type& that) const noexcept + { + return this->base_type != that.base_type; + } + procedure_type::procedure_type(std::vector> arguments, const std::size_t byte_size) : arguments(std::move(arguments)), type(byte_size) { } + + bool procedure_type::operator==(const procedure_type &that) const noexcept + { + return this->arguments == that.arguments; + } + + bool procedure_type::operator!=(const procedure_type &that) const noexcept + { + return this->arguments != that.arguments; + } + + bool operator==(const type& lhs, const type& rhs) noexcept + { + { + auto lhs_type = dynamic_cast(&lhs); + auto rhs_type = dynamic_cast(&rhs); + + if (lhs_type != nullptr && rhs_type != nullptr) + { + return *lhs_type == *rhs_type; + } + } + { + auto lhs_type = dynamic_cast(&lhs); + auto rhs_type = dynamic_cast(&rhs); + + if (lhs_type != nullptr && rhs_type != nullptr) + { + return *lhs_type == *rhs_type; + } + } + { + auto lhs_type = dynamic_cast(&lhs); + auto rhs_type = dynamic_cast(&rhs); + + if (lhs_type != nullptr && rhs_type != nullptr) + { + return *lhs_type == *rhs_type; + } + } + return false; + } + + bool operator!=(const type& lhs, const type& rhs) noexcept + { + return !(lhs == rhs); + } } diff --git a/tools/tester.js b/tools/tester.js index 210f085..cb6b633 100644 --- a/tools/tester.js +++ b/tools/tester.js @@ -32,11 +32,13 @@ async function compileTest (parsedPath) { }) } -function checkFailure (parsedPath, buildResult) { +async function checkFailure (parsedPath, buildResult) { + const failureFile = path.resolve('./tests/failures', `${parsedPath.name}.txt`) + + await fs.access(failureFile) const diffArguments = [ '-u', '--color=always', - path.resolve('./tests/failures', `${parsedPath.name}.txt`), - '-' + failureFile, '-' ] try { @@ -97,16 +99,22 @@ async function runVM(cpioImage) { async function runInDirectory (directoryPath) { let failed = 0 + const sources = await glob(path.join(directoryPath, '*.eln')) - for (const testEntry of await glob(path.join(directoryPath, '*.eln'))) { + for (const testEntry of sources) { const parsedPath = path.parse(testEntry) console.log(`Compiling ${parsedPath.base}.`) const buildResult = await compileTest(parsedPath) if (buildResult.code !== 0) { - if (!(await checkFailure(parsedPath, buildResult))) { + try { + if (!(await checkFailure(parsedPath, buildResult))) { + ++failed + } + } catch (e) { ++failed + console.log(buildResult.output) } continue } @@ -119,7 +127,7 @@ async function runInDirectory (directoryPath) { await runVM(cpioImage) return { - total: (await glob(path.join(directoryPath, 'failures/*'))).length, + total: sources.length, failed, passed () {