From 812bebb727b263d073750c2817d70c998e8c2cf3 Mon Sep 17 00:00:00 2001 From: Kevin Traini Date: Thu, 19 Sep 2024 19:09:31 +0200 Subject: [PATCH] feat: Add validation for calcul Part of #25 - check left operand - check right operand - check calcul can be done between types Also add early return when type given by sub-expression is nullptr: it means there is an error, so no need to go further in this expression --- .gitignore | 1 + include/filc/validation/CalculValidator.h | 46 +++++++++++++ src/grammar/FilParser.g4 | 7 +- src/validation/CalculValidator.cpp | 67 +++++++++++++++++++ src/validation/ValidationVisitor.cpp | 42 +++++++++++- tests/unit/FilCompilerTest.cpp | 6 ++ tests/unit/validation/CalculValidatorTest.cpp | 52 ++++++++++++++ .../unit/validation/ValidationVisitorTest.cpp | 4 +- 8 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 include/filc/validation/CalculValidator.h create mode 100644 src/validation/CalculValidator.cpp create mode 100644 tests/unit/validation/CalculValidatorTest.cpp diff --git a/.gitignore b/.gitignore index f182499..140cff5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ coverage.info .idea FilLexer.tokens node_modules +vgcore.* diff --git a/include/filc/validation/CalculValidator.h b/include/filc/validation/CalculValidator.h new file mode 100644 index 0000000..9ec30fd --- /dev/null +++ b/include/filc/validation/CalculValidator.h @@ -0,0 +1,46 @@ +/** + * MIT License + * + * Copyright (c) 2024-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef FILC_CALCULVALIDATOR_H +#define FILC_CALCULVALIDATOR_H + +#include "filc/grammar/Type.h" +#include +#include + +namespace filc { +class CalculValidator { + public: + [[nodiscard]] static auto isCalculValid(const std::shared_ptr &left_type, const std::string &op, + const std::shared_ptr &right_type) -> bool; + + private: + [[nodiscard]] static auto isNumericOperatorValid(const std::string &op) -> bool; + + [[nodiscard]] static auto isBoolOperatorValid(const std::string &op) -> bool; + + [[nodiscard]] static auto isPointerOperatorValid(const std::string &op) -> bool; +}; +} + +#endif // FILC_CALCULVALIDATOR_H diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index 78ea707..4a0fe7a 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -141,8 +141,7 @@ assignation returns[std::shared_ptr tree] $tree = std::make_shared($i1.text, $e1.tree); } | i2=IDENTIFIER op=(PLUS_EQ | MINUS_EQ | STAR_EQ | DIV_EQ | MOD_EQ | AND_EQ | OR_EQ) e2=expression { - $tree = std::make_shared( - $i2.text, - std::make_shared(std::make_shared($i2.text), $op.text.substr(0, $op.text.size() - 1), $e2.tree) - ); + const auto calcul = std::make_shared(std::make_shared($i2.text), $op.text.substr(0, $op.text.size() - 1), $e2.tree); + calcul->setPosition(filc::Position($op, $e2.stop)); + $tree = std::make_shared($i2.text, calcul); }; diff --git a/src/validation/CalculValidator.cpp b/src/validation/CalculValidator.cpp new file mode 100644 index 0000000..3e4959e --- /dev/null +++ b/src/validation/CalculValidator.cpp @@ -0,0 +1,67 @@ +/** + * MIT License + * + * Copyright (c) 2024-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "filc/validation/CalculValidator.h" +#include +#include + +using namespace filc; + +auto CalculValidator::isCalculValid(const std::shared_ptr &left_type, const std::string &op, + const std::shared_ptr &right_type) -> bool { + if (left_type != right_type) { + return false; + } + const auto type = left_type->getName(); + + const std::vector numeric_type = { + "i8", "i16", "i32", "i64", "i128", "u8", "u16", "u32", "u64", "u128", "f32", "f64", + }; + if (std::find(numeric_type.begin(), numeric_type.end(), type) != numeric_type.end()) { + return isNumericOperatorValid(op); + } + + if (type == "bool") { + return isBoolOperatorValid(op); + } + + if (type[type.length() - 1] == '*') { // A pointer + return isPointerOperatorValid(op); + } + + // We don't know what it is, so we assert it cannot be done + return false; +} + +auto CalculValidator::isNumericOperatorValid(const std::string &op) -> bool { + const std::vector valid_op = { + "%", "+", "-", "/", "*", "<", "<=", ">", ">=", "==", "!=", + }; + return std::find(valid_op.begin(), valid_op.end(), op) != valid_op.end(); +} + +auto CalculValidator::isBoolOperatorValid(const std::string &op) -> bool { + return op == "&&" || op == "||" || op == "==" || op == "!="; +} + +auto CalculValidator::isPointerOperatorValid(const std::string &op) -> bool { return op == "==" || op == "!="; } diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp index 2a78b7e..784f4ab 100644 --- a/src/validation/ValidationVisitor.cpp +++ b/src/validation/ValidationVisitor.cpp @@ -23,11 +23,13 @@ */ #include "filc/validation/ValidationVisitor.h" #include "filc/grammar/assignation/Assignation.h" +#include "filc/grammar/calcul/Calcul.h" #include "filc/grammar/identifier/Identifier.h" #include "filc/grammar/literal/Literal.h" #include "filc/grammar/program/Program.h" #include "filc/grammar/variable/Variable.h" #include "filc/utils/Message.h" +#include "filc/validation/CalculValidator.h" #include using namespace filc; @@ -47,6 +49,10 @@ auto ValidationVisitor::visitProgram(Program *program) -> void { if (it + 1 == expressions.end()) { const auto expected = _environment->getType("int"); const auto found_type = (*it)->getType(); + if (found_type == nullptr) { + return; + } + if (found_type != expected) { auto found_type_name = found_type->getDisplayName() != found_type->getName() ? found_type->getDisplayName() + " aka " + found_type->getName() @@ -121,6 +127,9 @@ auto ValidationVisitor::visitVariableDeclaration(VariableDeclaration *variable) return; } variable_type = _environment->getType(variable->getTypeName()); + if (variable_type == nullptr) { + return; + } } if (variable->getValue() != nullptr) { @@ -175,7 +184,35 @@ auto ValidationVisitor::visitIdentifier(Identifier *identifier) -> void { } auto ValidationVisitor::visitBinaryCalcul(BinaryCalcul *calcul) -> void { - throw std::logic_error("Not implemented yet"); + _context->stack(); + _context->set("return", true); + calcul->getLeftExpression()->accept(this); + const auto left_type = calcul->getLeftExpression()->getType(); + _context->unstack(); + + _context->stack(); + _context->set("return", true); + calcul->getRightExpression()->accept(this); + const auto right_type = calcul->getRightExpression()->getType(); + _context->unstack(); + + if (left_type == nullptr || right_type == nullptr) { + return; + } + + if (!CalculValidator::isCalculValid(left_type, calcul->getOperator(), right_type)) { + _out << Message(ERROR, + "You cannot use operator " + calcul->getOperator() + " with " + left_type->getDisplayName() + + " and " + right_type->getDisplayName(), + calcul->getPosition(), ERROR_COLOR); + return; + } + + calcul->setType(left_type); + + if (!_context->has("return") || !_context->get("return")) { + _out << Message(WARNING, "Value not used", calcul->getPosition(), WARNING_COLOR); + } } auto ValidationVisitor::visitAssignation(Assignation *assignation) -> void { @@ -195,6 +232,9 @@ auto ValidationVisitor::visitAssignation(Assignation *assignation) -> void { assignation->getValue()->accept(this); _context->unstack(); const auto value_type = assignation->getValue()->getType(); + if (value_type == nullptr) { + return; + } if (value_type->getName() != name.getType()->getName()) { const auto variable_type_dump = name.getType()->getDisplayName() != name.getType()->getName() ? name.getType()->getDisplayName() + " aka " + name.getType()->getName() diff --git a/tests/unit/FilCompilerTest.cpp b/tests/unit/FilCompilerTest.cpp index d320893..1a445e2 100644 --- a/tests/unit/FilCompilerTest.cpp +++ b/tests/unit/FilCompilerTest.cpp @@ -72,3 +72,9 @@ TEST(FilCompiler, dumpAST) { "=== End AST dump ===\n", result.c_str()); } + +TEST(FilCompiler, fullRun) { + std::stringstream ss; + auto compiler = filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(ss), filc::ValidationVisitor(std::cout)); + ASSERT_EQ(1, compiler.run(2, toStringArray({"filc", FIXTURES_PATH "/sample.fil"}).data())); +} diff --git a/tests/unit/validation/CalculValidatorTest.cpp b/tests/unit/validation/CalculValidatorTest.cpp new file mode 100644 index 0000000..7ceb4d7 --- /dev/null +++ b/tests/unit/validation/CalculValidatorTest.cpp @@ -0,0 +1,52 @@ +/** + * MIT License + * + * Copyright (c) 2024-Present Kevin Traini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include +#include + +TEST(CalculValidator, invalidDifferentType) { + ASSERT_FALSE( + filc::CalculValidator::isCalculValid(std::make_shared("a"), "", std::make_shared("b"))); +} + +TEST(CalculValidator, validNumeric) { + ASSERT_TRUE(filc::CalculValidator::isCalculValid(std::make_shared("i32"), "+", + std::make_shared("i32"))); +} + +TEST(CalculValidator, validBool) { + ASSERT_TRUE(filc::CalculValidator::isCalculValid(std::make_shared("bool"), "&&", + std::make_shared("bool"))); +} + +TEST(CalculValidator, validPointer) { + ASSERT_TRUE(filc::CalculValidator::isCalculValid(std::make_shared("i32*"), + "==", std::make_shared("i32*"))); +} + +TEST(CalculValidator, invalidUnknown) { + ASSERT_FALSE(filc::CalculValidator::isCalculValid(std::make_shared("foo"), "+", + std::make_shared("foo"))); +} diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp index 8349423..c283646 100644 --- a/tests/unit/validation/ValidationVisitorTest.cpp +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -110,7 +110,9 @@ TEST(ValidationVisitor, visitIdentifier) { TEST(ValidationVisitor, visitBinaryCalcul) { VISITOR; const auto program = filc::ParserProxy::parse(VALIDATION_FIXTURES "/binary.fil"); - ASSERT_THROW(program->accept(&visitor), std::logic_error); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_STREQ("int", program->getExpressions()[0]->getType()->getDisplayName().c_str()); } TEST(ValidationVisitor, visitAssignation) {