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/filc.h b/include/filc/filc.h index 700f8cf..30cac5d 100644 --- a/include/filc/filc.h +++ b/include/filc/filc.h @@ -27,17 +27,19 @@ #include "filc/grammar/Parser.h" #include "filc/options/OptionsParser.h" #include "filc/grammar/DumpVisitor.h" +#include "filc/validation/ValidationVisitor.h" namespace filc { class FilCompiler final { public: - FilCompiler(OptionsParser options_parser, DumpVisitor ast_dump_visitor); + FilCompiler(OptionsParser options_parser, DumpVisitor ast_dump_visitor, ValidationVisitor validation_visitor); auto run(int argc, char **argv) -> int; private: OptionsParser _options_parser; DumpVisitor _ast_dump_visitor; + ValidationVisitor _validation_visitor; }; } // namespace filc diff --git a/include/filc/grammar/Position.h b/include/filc/grammar/Position.h new file mode 100644 index 0000000..b174626 --- /dev/null +++ b/include/filc/grammar/Position.h @@ -0,0 +1,55 @@ +/** + * 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_POSITION_H +#define FILC_POSITION_H + +#include "antlr4-runtime.h" +#include +#include + +namespace filc { +class Position { + public: + Position(); + + Position(const antlr4::Token* start_token, const antlr4::Token* end_token); + + [[nodiscard]] auto getFilename() const -> std::string; + + [[nodiscard]] auto getStartPosition() const -> std::pair; + + [[nodiscard]] auto getEndPosition() const -> std::pair; + + [[nodiscard]] auto getContent() const -> std::vector; + + [[nodiscard]] auto dump(const std::string &color) const -> std::string; + + private: + std::string _filename; + std::pair _start_position; + std::pair _end_position; +}; +} + +#endif // FILC_POSITION_H diff --git a/include/filc/grammar/Type.h b/include/filc/grammar/Type.h new file mode 100644 index 0000000..9beed91 --- /dev/null +++ b/include/filc/grammar/Type.h @@ -0,0 +1,90 @@ +/** + * 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_TYPE_H +#define FILC_TYPE_H + +#include +#include + +namespace filc { +class AbstractType { + public: + [[nodiscard]] virtual auto getName() const noexcept -> std::string = 0; + + [[nodiscard]] virtual auto getDisplayName() const noexcept -> std::string = 0; + + [[nodiscard]] virtual auto toDisplay() const noexcept -> std::string = 0; + + protected: + AbstractType() = default; +}; + +class Type final: public AbstractType { + public: + explicit Type(std::string name); + + [[nodiscard]] auto getName() const noexcept -> std::string override; + + [[nodiscard]] auto getDisplayName() const noexcept -> std::string override; + + [[nodiscard]] auto toDisplay() const noexcept -> std::string override; + + private: + std::string _name; +}; + +class PointerType final: public AbstractType { + public: + explicit PointerType(std::shared_ptr pointed_type); + + [[nodiscard]] auto getName() const noexcept -> std::string override; + + [[nodiscard]] auto getDisplayName() const noexcept -> std::string override; + + [[nodiscard]] auto toDisplay() const noexcept -> std::string override; + + private: + std::shared_ptr _pointed_type; +}; + +class AliasType final: public AbstractType { + public: + AliasType(std::string name, std::shared_ptr aliased_type); + + [[nodiscard]] auto getName() const noexcept -> std::string override; + + [[nodiscard]] auto getDisplayName() const noexcept -> std::string override; + + [[nodiscard]] auto toDisplay() const noexcept -> std::string override; + + private: + std::string _name; + std::shared_ptr _aliased_type; +}; +} + +auto operator==(const std::shared_ptr &a, const std::shared_ptr &b) -> bool; +auto operator!=(const std::shared_ptr &a, const std::shared_ptr &b) -> bool; + +#endif // FILC_TYPE_H diff --git a/include/filc/grammar/expression/Expression.h b/include/filc/grammar/expression/Expression.h index 13b2c9d..2827a6f 100644 --- a/include/filc/grammar/expression/Expression.h +++ b/include/filc/grammar/expression/Expression.h @@ -26,6 +26,8 @@ #include "filc/grammar/ast.h" #include "filc/grammar/Visitor.h" +#include "filc/grammar/Position.h" +#include "filc/grammar/Type.h" #include namespace filc { @@ -33,8 +35,20 @@ class Expression: public Visitable { public: virtual ~Expression() = default; + auto setPosition(const Position& position) -> void; + + [[nodiscard]] auto getPosition() const -> const Position&; + + auto setType(const std::shared_ptr &type) -> void; + + [[nodiscard]] auto getType() const -> const std::shared_ptr&; + protected: Expression(); + + private: + Position _position; + std::shared_ptr _type; }; } diff --git a/include/filc/utils/Message.h b/include/filc/utils/Message.h new file mode 100644 index 0000000..a333c84 --- /dev/null +++ b/include/filc/utils/Message.h @@ -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. + */ +#ifndef FILC_MESSAGE_H +#define FILC_MESSAGE_H + +#include "filc/grammar/Position.h" +#include + +#define WARNING "WARNING" +#define WARNING_COLOR "\033[33m" +#define ERROR "ERROR" +#define ERROR_COLOR "\033[31m" + +namespace filc { +class Message final { + public: + Message(std::string tag, std::string message, Position position, std::string color); + + auto write(std::ostream &out) const -> std::ostream &; + + private: + std::string _tag; + std::string _message; + Position _position; + std::string _color; +}; +} + +auto operator<<(std::ostream &out, const filc::Message &message) -> std::ostream&; + +#endif // FILC_MESSAGE_H 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/include/filc/validation/Environment.h b/include/filc/validation/Environment.h new file mode 100644 index 0000000..28c5521 --- /dev/null +++ b/include/filc/validation/Environment.h @@ -0,0 +1,55 @@ +/** + * 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_ENVIRONMENT_H +#define FILC_ENVIRONMENT_H + +#include "filc/grammar/Type.h" +#include "filc/validation/Name.h" +#include +#include + +namespace filc { +class Environment { + public: + Environment(); + + [[nodiscard]] auto hasType(const std::string &name) const -> bool; + + [[nodiscard]] auto getType(const std::string &name) const -> const std::shared_ptr &; + + auto addType(const std::shared_ptr &type) -> void; + + [[nodiscard]] auto hasName(const std::string &name) const -> bool; + + [[nodiscard]] auto getName(const std::string &name) const -> const Name&; + + auto addName(const Name &name) -> void; + + private: + std::map> _types; + std::map _names; +}; +} + +#endif // FILC_ENVIRONMENT_H diff --git a/include/filc/validation/Name.h b/include/filc/validation/Name.h new file mode 100644 index 0000000..982ec92 --- /dev/null +++ b/include/filc/validation/Name.h @@ -0,0 +1,51 @@ +/** + * 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_NAME_H +#define FILC_NAME_H + +#include "filc/grammar/Type.h" +#include +#include + +namespace filc { +class Name { + public: + Name(); + + Name(bool constant, std::string name, std::shared_ptr type); + + [[nodiscard]] auto isConstant() const -> bool; + + [[nodiscard]] auto getName() const -> const std::string&; + + [[nodiscard]] auto getType() const -> std::shared_ptr; + + private: + bool _constant; + std::string _name; + std::shared_ptr _type; +}; +} + +#endif // FILC_NAME_H diff --git a/include/filc/validation/ValidationVisitor.h b/include/filc/validation/ValidationVisitor.h new file mode 100644 index 0000000..0444bda --- /dev/null +++ b/include/filc/validation/ValidationVisitor.h @@ -0,0 +1,103 @@ +/** + * 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_VALIDATIONVISITOR_H +#define FILC_VALIDATIONVISITOR_H + +#include "filc/grammar/Visitor.h" +#include "filc/grammar/Position.h" +#include "filc/validation/Environment.h" +#include +#include +#include +#include +#include +#include + +namespace filc { +class ValidationContext final { + public: + ValidationContext(); + + auto stack() -> void; + + auto unstack() -> void; + + auto set(const std::string &key, const std::any &value) -> void; + + [[nodiscard]] auto has(const std::string &key) const -> bool; + + template + auto get(const std::string &key) const -> T { + if (_values.top().find(key) == _values.top().end()) { + throw std::logic_error("There is not value for key: " + key); + } + + return std::any_cast(_values.top().at(key)); + } + + auto clear() -> void; + + private: + std::stack> _values; +}; + +class ValidationVisitor final : public Visitor { + public: + explicit ValidationVisitor(std::ostream &out); + + [[nodiscard]] auto hasError() const -> bool; + + auto visitProgram(Program *program) -> void override; + + auto visitBooleanLiteral(BooleanLiteral *literal) -> void override; + + auto visitIntegerLiteral(IntegerLiteral *literal) -> void override; + + auto visitFloatLiteral(FloatLiteral *literal) -> void override; + + auto visitCharacterLiteral(CharacterLiteral *literal) -> void override; + + auto visitStringLiteral(StringLiteral *literal) -> void override; + + auto visitVariableDeclaration(VariableDeclaration *variable) -> void override; + + auto visitIdentifier(Identifier *identifier) -> void override; + + auto visitBinaryCalcul(BinaryCalcul *calcul) -> void override; + + auto visitAssignation(Assignation *assignation) -> void override; + + private: + std::unique_ptr _context; + std::unique_ptr _environment; + std::ostream &_out; + bool _error; + + auto displayError(const std::string &message, const Position &position) -> void; + + auto displayWarning(const std::string &message, const Position &position) const -> void; +}; +} + +#endif // FILC_VALIDATIONVISITOR_H diff --git a/main.cpp b/main.cpp index aa218ae..c929002 100644 --- a/main.cpp +++ b/main.cpp @@ -24,6 +24,7 @@ #include auto main(int argc, char **argv) -> int { - auto compiler = filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(std::cout)); + auto compiler = + filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(std::cout), filc::ValidationVisitor(std::cout)); return compiler.run(argc, argv); } diff --git a/src/filc.cpp b/src/filc.cpp index 7ebb0ab..cfadb34 100644 --- a/src/filc.cpp +++ b/src/filc.cpp @@ -29,8 +29,10 @@ using namespace filc; -FilCompiler::FilCompiler(OptionsParser options_parser, DumpVisitor ast_dump_visitor) - : _options_parser(std::move(options_parser)), _ast_dump_visitor(std::move(ast_dump_visitor)) {} +FilCompiler::FilCompiler(OptionsParser options_parser, DumpVisitor ast_dump_visitor, + ValidationVisitor validation_visitor) + : _options_parser(std::move(options_parser)), _ast_dump_visitor(std::move(ast_dump_visitor)), + _validation_visitor(std::move(validation_visitor)) {} auto FilCompiler::run(int argc, char **argv) -> int { _options_parser.parse(argc, argv); @@ -58,5 +60,10 @@ auto FilCompiler::run(int argc, char **argv) -> int { } } - return 1; + program->accept(&_validation_visitor); + if (_validation_visitor.hasError()) { + return 1; + } + + return 0; } diff --git a/src/grammar/FilParser.g4 b/src/grammar/FilParser.g4 index 3f157ea..4a0fe7a 100644 --- a/src/grammar/FilParser.g4 +++ b/src/grammar/FilParser.g4 @@ -51,6 +51,9 @@ program returns[std::shared_ptr tree] })* EOF; expression returns[std::shared_ptr tree] +@after { + $tree->setPosition(filc::Position($ctx->start, $ctx->stop)); +} : l=literal { $tree = $l.tree; } @@ -138,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/grammar/Parser.cpp b/src/grammar/Parser.cpp index aeada50..4d1e645 100644 --- a/src/grammar/Parser.cpp +++ b/src/grammar/Parser.cpp @@ -25,10 +25,15 @@ #include "FilLexer.h" #include "FilParser.h" #include "antlr4-runtime.h" +#include using namespace filc; auto ParserProxy::parse(const std::string &filename) -> std::shared_ptr { + if (!std::filesystem::exists(filename)) { + throw std::logic_error("File '" + filename + "' not found"); + } + antlr4::ANTLRFileStream input; input.loadFromFile(filename); FilLexer lexer(&input); diff --git a/src/grammar/Position.cpp b/src/grammar/Position.cpp new file mode 100644 index 0000000..2ea5030 --- /dev/null +++ b/src/grammar/Position.cpp @@ -0,0 +1,124 @@ +/** + * 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/grammar/Position.h" +#include + +using namespace filc; + +#define RESET "\033[0m" + +Position::Position() : _start_position(0, 0), _end_position(0, 0) {} + +Position::Position(const antlr4::Token *start_token, const antlr4::Token *end_token) + : _filename(start_token->getTokenSource()->getSourceName()) { + if (end_token->getTokenSource()->getSourceName() != _filename) { + throw std::logic_error("start and end token are not from the same source file"); + } + + _start_position = std::make_pair(start_token->getLine(), start_token->getCharPositionInLine()); + _end_position = std::make_pair(end_token->getLine(), end_token->getCharPositionInLine()); +} + +auto Position::getFilename() const -> std::string { return _filename; } + +auto Position::getStartPosition() const -> std::pair { return _start_position; } + +auto Position::getEndPosition() const -> std::pair { return _end_position; } + +auto Position::getContent() const -> std::vector { + if (_filename.empty() || _filename == "") { + return {}; + } + + std::ifstream file(_filename); + if (!file.is_open() || !file.good()) { + throw std::logic_error("File '" + _filename + "' not found"); + } + + std::vector content; + + const auto start_line = _start_position.first; + unsigned int index = 0; + std::string line; + for (; index < start_line; index++) { + std::getline(file, line); + } + content.push_back(line); + + const auto end_line = _end_position.first; + if (end_line > start_line) { + for (; index < end_line; index++) { + std::getline(file, line); + content.push_back(line); + } + } + + file.close(); + + return content; +} + +auto Position::dump(const std::string &color) const -> std::string { + const auto start_line = _start_position.first; + const auto start_column = _start_position.second; + const auto end_line = _end_position.first; + const auto end_column = _end_position.second; + const auto content = getContent(); + if (content.empty()) { + return ""; + } + + if (content.size() == 1) { // Single line + const auto nth = " " + std::to_string(start_line) + " "; + const auto spaces = start_column > 0 ? std::string(start_column, ' ') : ""; + return std::string(nth.length() - 1, ' ') + "--> " + _filename + ":" + std::to_string(start_line) + ":" + + std::to_string(start_column) + "\n" + nth + "| " + content[0] + "\n" + std::string(nth.length(), ' ') + + "| " + spaces + color + "^" + RESET + "\n"; + } + + if (content.size() > 1) { // Multi line + std::vector nths; + const auto nth_end = " " + std::to_string(end_line) + " "; + for (unsigned int i = start_line; i <= end_line; i++) { + const auto line = " " + std::to_string(i) + " "; + nths.push_back(line + std::string(nth_end.length() - line.length(), ' ')); + } + const auto nth_spaces = std::string(nth_end.length(), ' ') + "| "; + const auto start_spaces = start_column > 0 ? std::string(start_column, ' ') : ""; + const auto end_spaces = end_column > 0 ? std::string(end_column, ' ') : ""; + + auto res = std::string(nth_end.length() - 1, ' ') + "--> " + _filename + ":" + std::to_string(start_line) + + ":" + std::to_string(start_column) + "\n" + nth_spaces + start_spaces + color + "v" + RESET + "\n"; + + for (unsigned int i = 0; i < nths.size(); i++) { + res += nths[i] + "| " + content[i] + "\n"; + } + + res += nth_spaces + end_spaces + color + "^" + RESET + "\n"; + + return res; + } + + throw std::logic_error("Position content is empty"); +} diff --git a/src/grammar/Type.cpp b/src/grammar/Type.cpp new file mode 100644 index 0000000..fe1d210 --- /dev/null +++ b/src/grammar/Type.cpp @@ -0,0 +1,65 @@ +/** + * 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/grammar/Type.h" +#include + +using namespace filc; + +Type::Type(std::string name) : _name(std::move(name)) {} + +auto Type::getName() const noexcept -> std::string { return _name; } + +auto Type::getDisplayName() const noexcept -> std::string { return getName(); } + +auto Type::toDisplay() const noexcept -> std::string { return getName(); } + +PointerType::PointerType(std::shared_ptr pointed_type) : _pointed_type(std::move(pointed_type)) {} + +auto PointerType::getName() const noexcept -> std::string { return _pointed_type->getName() + "*"; } + +auto PointerType::getDisplayName() const noexcept -> std::string { return _pointed_type->getDisplayName() + "*"; } + +auto PointerType::toDisplay() const noexcept -> std::string { + if (_pointed_type->getName() != _pointed_type->getDisplayName()) { + return getDisplayName() + " aka " + getName(); + } + return getName(); +} + +AliasType::AliasType(std::string name, std::shared_ptr aliased_type) + : _name(std::move(name)), _aliased_type(std::move(aliased_type)) {} + +auto AliasType::getName() const noexcept -> std::string { return _aliased_type->getName(); } + +auto AliasType::getDisplayName() const noexcept -> std::string { return _name; } + +auto AliasType::toDisplay() const noexcept -> std::string { return getDisplayName() + " aka " + getName(); } + +auto operator==(const std::shared_ptr &a, const std::shared_ptr &b) -> bool { + return a->getName() == b->getName(); +} + +auto operator!=(const std::shared_ptr &a, const std::shared_ptr &b) -> bool { + return !(a == b); +} diff --git a/src/grammar/expression/Expression.cpp b/src/grammar/expression/Expression.cpp index 2b1655f..4a190f9 100644 --- a/src/grammar/expression/Expression.cpp +++ b/src/grammar/expression/Expression.cpp @@ -26,3 +26,11 @@ using namespace filc; Expression::Expression() = default; + +auto Expression::setPosition(const Position &position) -> void { _position = position; } + +auto Expression::getPosition() const -> const Position & { return _position; } + +auto Expression::setType(const std::shared_ptr &type) -> void { _type = type; } + +auto Expression::getType() const -> const std::shared_ptr & { return _type; } diff --git a/src/utils/Message.cpp b/src/utils/Message.cpp new file mode 100644 index 0000000..dfbe213 --- /dev/null +++ b/src/utils/Message.cpp @@ -0,0 +1,39 @@ +/** + * 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/utils/Message.h" +#include + +using namespace filc; + +#define BOLD "\033[1m" +#define RESET "\033[0m" + +Message::Message(std::string tag, std::string message, Position position, std::string color) + : _tag(std::move(tag)), _message(std::move(message)), _position(std::move(position)), _color(std::move(color)) {} + +auto Message::write(std::ostream &out) const -> std::ostream & { + return out << BOLD << _color << "[" << _tag << "] " << RESET << _message << "\n" << _position.dump(_color); +} + +auto operator<<(std::ostream &out, const filc::Message &message) -> std::ostream & { return message.write(out); } 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/Environment.cpp b/src/validation/Environment.cpp new file mode 100644 index 0000000..423260b --- /dev/null +++ b/src/validation/Environment.cpp @@ -0,0 +1,83 @@ +/** + * 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/Environment.h" +#include + +using namespace filc; + +Environment::Environment() { + addType(std::make_shared("i8")); + addType(std::make_shared("i16")); + addType(std::make_shared("i32")); + addType(std::make_shared("i64")); + addType(std::make_shared("i128")); + addType(std::make_shared("int", getType("i32"))); + + addType(std::make_shared("u8")); + addType(std::make_shared("u16")); + addType(std::make_shared("u32")); + addType(std::make_shared("u64")); + addType(std::make_shared("u128")); + addType(std::make_shared("uint", getType("u32"))); + + addType(std::make_shared("f32")); + addType(std::make_shared("f64")); + + addType(std::make_shared("bool")); + + addType(std::make_shared("char", getType("u8"))); + addType(std::make_shared(getType("char"))); +} + +auto Environment::hasType(const std::string &name) const -> bool { return _types.find(name) != _types.end(); } + +auto Environment::getType(const std::string &name) const -> const std::shared_ptr & { + if (!hasType(name)) { + throw std::logic_error("Environment doesn't have type " + name); + } + return _types.at(name); +} + +auto Environment::addType(const std::shared_ptr &type) -> void { + if (hasType(type->getDisplayName())) { + throw std::logic_error("Environment already have type " + type->getDisplayName() + " aka " + type->getName()); + } + _types[type->getDisplayName()] = type; +} + +auto Environment::hasName(const std::string &name) const -> bool { return _names.find(name) != _names.end(); } + +auto Environment::getName(const std::string &name) const -> const Name & { + if (!hasName(name)) { + throw std::logic_error("Environment doesn't have name " + name); + } + return _names.at(name); +} + +auto Environment::addName(const filc::Name &name) -> void { + if (hasName(name.getName())) { + throw std::logic_error("Environment already have name " + name.getName()); + } + _names[name.getName()] = name; +} diff --git a/src/validation/Name.cpp b/src/validation/Name.cpp new file mode 100644 index 0000000..8c3c835 --- /dev/null +++ b/src/validation/Name.cpp @@ -0,0 +1,38 @@ +/** + * 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/Name.h" +#include + +using namespace filc; + +Name::Name() : _constant(true) {} + +Name::Name(bool constant, std::string name, std::shared_ptr type) + : _constant(constant), _name(std::move(name)), _type(std::move(type)) {} + +auto Name::isConstant() const -> bool { return _constant; } + +auto Name::getName() const -> const std::string & { return _name; } + +auto Name::getType() const -> std::shared_ptr { return _type; } diff --git a/src/validation/ValidationContext.cpp b/src/validation/ValidationContext.cpp new file mode 100644 index 0000000..89a7c5a --- /dev/null +++ b/src/validation/ValidationContext.cpp @@ -0,0 +1,44 @@ +/** + * 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/ValidationVisitor.h" + +using namespace filc; + +ValidationContext::ValidationContext() { stack(); } + +auto ValidationContext::stack() -> void { _values.emplace(); } + +auto ValidationContext::unstack() -> void { + if (_values.size() > 1) { + _values.pop(); + } +} + +auto ValidationContext::set(const std::string &key, const std::any &value) -> void { _values.top()[key] = value; } + +auto ValidationContext::has(const std::string &key) const -> bool { + return _values.top().find(key) != _values.top().end(); +} + +auto ValidationContext::clear() -> void { _values.top().clear(); } diff --git a/src/validation/ValidationVisitor.cpp b/src/validation/ValidationVisitor.cpp new file mode 100644 index 0000000..07a2a88 --- /dev/null +++ b/src/validation/ValidationVisitor.cpp @@ -0,0 +1,238 @@ +/** + * 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/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; + +ValidationVisitor::ValidationVisitor(std::ostream &out) + : _context(new ValidationContext()), _environment(new Environment()), _out(out), _error(false) {} + +auto ValidationVisitor::hasError() const -> bool { return _error; } + +auto ValidationVisitor::displayError(const std::string &message, const Position &position) -> void { + _error = true; + _out << Message(ERROR, message, position, ERROR_COLOR); +} + +auto ValidationVisitor::displayWarning(const std::string &message, const Position &position) const -> void { + _out << Message(WARNING, message, position, WARNING_COLOR); +} + +auto ValidationVisitor::visitProgram(Program *program) -> void { + auto expressions = program->getExpressions(); + for (auto it = expressions.begin(); it != expressions.end(); it++) { + if (it + 1 == expressions.end()) { + _context->set("return", true); + } + + (*it)->accept(this); + + 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) { + displayError("Expected type " + expected->toDisplay() + " but got " + found_type->toDisplay(), + (*it)->getPosition()); + } + } + + _context->clear(); + } +} + +auto ValidationVisitor::visitBooleanLiteral(BooleanLiteral *literal) -> void { + literal->setType(_environment->getType("bool")); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Boolean value not used", literal->getPosition()); + } +} + +auto ValidationVisitor::visitIntegerLiteral(IntegerLiteral *literal) -> void { + literal->setType(_environment->getType("int")); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Integer value not used", literal->getPosition()); + } +} + +auto ValidationVisitor::visitFloatLiteral(FloatLiteral *literal) -> void { + literal->setType(_environment->getType("f64")); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Float value not used", literal->getPosition()); + } +} + +auto ValidationVisitor::visitCharacterLiteral(CharacterLiteral *literal) -> void { + literal->setType(_environment->getType("char")); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Character value not used", literal->getPosition()); + } +} + +auto ValidationVisitor::visitStringLiteral(StringLiteral *literal) -> void { + literal->setType(_environment->getType("char*")); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("String value not used", literal->getPosition()); + } +} + +auto ValidationVisitor::visitVariableDeclaration(VariableDeclaration *variable) -> void { + if (_environment->hasName(variable->getName())) { + displayError(variable->getName() + " is already defined", variable->getPosition()); + return; + } + + if (variable->isConstant() && variable->getValue() == nullptr) { + displayError("When declaring a constant, you must provide it a value", variable->getPosition()); + return; + } + + std::shared_ptr variable_type = nullptr; + if (!variable->getTypeName().empty()) { + if (!_environment->hasType(variable->getTypeName())) { + displayError("Unknown type: " + variable->getTypeName(), variable->getPosition()); + return; + } + variable_type = _environment->getType(variable->getTypeName()); + } + + if (variable->getValue() != nullptr) { + _context->stack(); + _context->set("return", true); + variable->getValue()->accept(this); + _context->unstack(); + const auto value_type = variable->getValue()->getType(); + if (value_type == nullptr) { + return; + } + if (variable_type != nullptr && variable_type->getName() != value_type->getName()) { + displayError("Cannot assign value of type " + value_type->toDisplay() + " to a variable of type " + + variable_type->toDisplay(), + variable->getPosition()); + return; + } else if (variable_type == nullptr) { + variable_type = value_type; + } + } + + if (variable_type == nullptr) { + displayError("When declaring a variable, you must provide at least a type or a value", variable->getPosition()); + return; + } + + variable->setType(variable_type); + _environment->addName(Name(variable->isConstant(), variable->getName(), variable_type)); +} + +auto ValidationVisitor::visitIdentifier(Identifier *identifier) -> void { + if (!_environment->hasName(identifier->getName())) { + displayError("Unknown name, don't know what it refers to: " + identifier->getName(), identifier->getPosition()); + return; + } + + const auto name = _environment->getName(identifier->getName()); + identifier->setType(name.getType()); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Value not used", identifier->getPosition()); + } +} + +auto ValidationVisitor::visitBinaryCalcul(BinaryCalcul *calcul) -> void { + _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)) { + displayError("You cannot use operator " + calcul->getOperator() + " with " + left_type->toDisplay() + " and " + + right_type->toDisplay(), + calcul->getPosition()); + return; + } + + calcul->setType(left_type); + + if (!_context->has("return") || !_context->get("return")) { + displayWarning("Value not used", calcul->getPosition()); + } +} + +auto ValidationVisitor::visitAssignation(Assignation *assignation) -> void { + if (!_environment->hasName(assignation->getIdentifier())) { + displayError("Unknown name, don't know what it refers to: " + assignation->getIdentifier(), + assignation->getPosition()); + return; + } + const auto name = _environment->getName(assignation->getIdentifier()); + if (name.isConstant()) { + displayError("Cannot modify a constant", assignation->getPosition()); + return; + } + + _context->stack(); + _context->set("return", true); + 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()) { + displayError("Cannot assign value of type " + value_type->toDisplay() + " to a variable of type " + + name.getType()->toDisplay(), + assignation->getPosition()); + return; + } + + assignation->setType(name.getType()); +} diff --git a/tests/e2e/memory.cpp b/tests/e2e/memory.cpp index 0bedefc..ce9670b 100644 --- a/tests/e2e/memory.cpp +++ b/tests/e2e/memory.cpp @@ -52,8 +52,13 @@ TEST(Memory, filc_version) { ASSERT_THAT(result, ::testing::HasSubstr(VALGRIND_OUTPUT_ZERO)); } -TEST(Memory, filc_dump_all) { - const auto result = valgrind_run("--dump " FIXTURES_PATH "/sample.fil"); +TEST(Memory, filc_dump_ast) { + const auto result = valgrind_run("--dump=ast " FIXTURES_PATH "/sample.fil"); + ASSERT_THAT(result, ::testing::HasSubstr(VALGRIND_OUTPUT_ZERO)); +} + +TEST(Memory, filc_full) { + const auto result = valgrind_run(FIXTURES_PATH "/sample.fil"); ASSERT_THAT(result, ::testing::HasSubstr(VALGRIND_OUTPUT_ZERO)); } diff --git a/tests/unit/FilCompilerTest.cpp b/tests/unit/FilCompilerTest.cpp index 6db98a2..1a445e2 100644 --- a/tests/unit/FilCompilerTest.cpp +++ b/tests/unit/FilCompilerTest.cpp @@ -27,7 +27,8 @@ #include TEST(FilCompiler, run) { - auto compiler = filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(std::cout)); + auto compiler = + filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(std::cout), filc::ValidationVisitor(std::cout)); SCOPED_TRACE("No argument"); ASSERT_EQ(0, compiler.run(1, toStringArray({"filc"}).data())); @@ -41,7 +42,7 @@ TEST(FilCompiler, run) { TEST(FilCompiler, dumpAST) { std::stringstream ss; - auto compiler = filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(ss)); + auto compiler = filc::FilCompiler(filc::OptionsParser(), filc::DumpVisitor(ss), filc::ValidationVisitor(std::cout)); ASSERT_EQ(0, compiler.run(3, toStringArray({"filc", "--dump=ast", FIXTURES_PATH "/sample.fil"}).data())); std::string result(std::istreambuf_iterator(ss), {}); ASSERT_STREQ("=== Begin AST dump ===\n" @@ -71,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/Fixtures/ipsum.txt b/tests/unit/Fixtures/ipsum.txt new file mode 100644 index 0000000..59295af --- /dev/null +++ b/tests/unit/Fixtures/ipsum.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pharetra volutpat fermentum. +Phasellus fringilla sem vel lorem scelerisque, non commodo augue consectetur. Donec +malesuada erat vitae egestas euismod. Fusce in cursus justo, non egestas ipsum. Pellentesque +at dui nunc. Nam ligula augue, tempus id vestibulum nec, rutrum porttitor nibh. Nullam non +egestas nunc. Morbi nec mollis lacus. Aliquam erat volutpat. Nulla mauris dui, fringilla sit +amet condimentum sed, mattis nec ipsum. Nunc faucibus tincidunt urna, posuere euismod elit +ultrices eu. Nulla urna neque, posuere in vestibulum at, vulputate sit amet leo. Duis id +sodales ante. Curabitur fringilla ullamcorper nibh, vel dictum massa vehicula eget. Fusce +mollis efficitur dolor et facilisis. Nulla quis odio mi. diff --git a/tests/unit/grammar/ParserTest.cpp b/tests/unit/grammar/ParserTest.cpp index e4a791b..071af67 100644 --- a/tests/unit/grammar/ParserTest.cpp +++ b/tests/unit/grammar/ParserTest.cpp @@ -32,6 +32,8 @@ using namespace ::testing; +TEST(Parser, nonExistingFile) { ASSERT_THROW(filc::ParserProxy::parse("non-existing-file"), std::logic_error); } + TEST(Parser, parseSample) { const auto program = filc::ParserProxy::parse(FIXTURES_PATH "/sample.fil"); ASSERT_THAT(program->getExpressions(), SizeIs(11)); diff --git a/tests/unit/grammar/PositionTest.cpp b/tests/unit/grammar/PositionTest.cpp new file mode 100644 index 0000000..7bc5d90 --- /dev/null +++ b/tests/unit/grammar/PositionTest.cpp @@ -0,0 +1,93 @@ +/** + * 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 "test_tools.h" +#include +#include +#include + +using namespace ::testing; + +#define FILENAME FIXTURES_PATH "/ipsum.txt" + +TEST(Position, defaultConstructor) { + filc::Position position; + ASSERT_THAT(position.getStartPosition(), Pair(0, 0)); + ASSERT_THAT(position.getEndPosition(), Pair(0, 0)); + ASSERT_STREQ("", position.getFilename().c_str()); + ASSERT_THAT(position.getContent(), IsEmpty()); + ASSERT_STREQ("", position.dump("").c_str()); +} + +TEST(Position, tokenConstructor) { + filc::Position position(new TokenStub("source", {2, 3}), new TokenStub("source", {5, 6})); + ASSERT_THAT(position.getStartPosition(), Pair(2, 3)); + ASSERT_THAT(position.getEndPosition(), Pair(5, 6)); + ASSERT_STREQ("source", position.getFilename().c_str()); +} + +TEST(Position, tokenConstructor_throw) { + ASSERT_THROW(filc::Position position(new TokenStub("source1", {2, 3}), new TokenStub("source2", {5, 6})), + std::logic_error); +} + +TEST(Position, getContent) { + filc::Position position(new TokenStub(FILENAME, {2, 3}), new TokenStub(FILENAME, {4, 6})); + const auto content = position.getContent(); + ASSERT_THAT( + content, + ElementsAre("Phasellus fringilla sem vel lorem scelerisque, non commodo augue consectetur. Donec", // 2 + "malesuada erat vitae egestas euismod. Fusce in cursus justo, non egestas ipsum. Pellentesque", // 3 + "at dui nunc. Nam ligula augue, tempus id vestibulum nec, rutrum porttitor nibh. Nullam non" // 4 + )); +} + +TEST(Position, dump_multiline) { + filc::Position position(new TokenStub(FILENAME, {2, 3}), new TokenStub(FILENAME, {4, 6})); + const auto result = position.dump(""); + ASSERT_STREQ(" --> " FILENAME ":2:3\n" + " | v\x1B[0m\n" + " 2 | Phasellus fringilla sem vel lorem scelerisque, non commodo augue consectetur. Donec\n" + " 3 | malesuada erat vitae egestas euismod. Fusce in cursus justo, non egestas ipsum. Pellentesque\n" + " 4 | at dui nunc. Nam ligula augue, tempus id vestibulum nec, rutrum porttitor nibh. Nullam non\n" + " | ^\x1B[0m\n", + result.c_str()); +} + +TEST(Position, dump_oneline) { + filc::Position position(new TokenStub(FILENAME, {1, 7}), new TokenStub(FILENAME, {1, 10})); + const auto result = position.dump(""); + ASSERT_STREQ(" --> " FILENAME ":1:7\n" + " 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pharetra volutpat fermentum.\n" + " | ^\x1B[0m\n", + result.c_str()); +} + +TEST(Position, dump_color) { + filc::Position position(new TokenStub(FILENAME, {1, 7}), new TokenStub(FILENAME, {1, 10})); + const auto result = position.dump("\033[31m"); + ASSERT_STREQ(" --> " FILENAME ":1:7\n" + " 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pharetra volutpat fermentum.\n" + " | \x1B[31m^\x1B[0m\n", + result.c_str()); +} diff --git a/tests/unit/grammar/TypeTest.cpp b/tests/unit/grammar/TypeTest.cpp new file mode 100644 index 0000000..487bb37 --- /dev/null +++ b/tests/unit/grammar/TypeTest.cpp @@ -0,0 +1,55 @@ +/** + * 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 + +TEST(Type, getName) { + filc::Type type("my_type"); + ASSERT_STREQ("my_type", type.getName().c_str()); +} + +TEST(Type, getDisplayName) { + filc::Type type("another_type"); + ASSERT_STREQ("another_type", type.getDisplayName().c_str()); +} + +TEST(PointerType, getName) { + filc::PointerType type(std::make_shared("int")); + ASSERT_STREQ("int*", type.getName().c_str()); +} + +TEST(PointerType, getDisplayName) { + filc::PointerType type(std::make_shared("int")); + ASSERT_STREQ("int*", type.getDisplayName().c_str()); +} + +TEST(AliasType, getName) { + filc::AliasType type("char", std::make_shared("u8")); + ASSERT_STREQ("u8", type.getName().c_str()); +} + +TEST(AliasType, getDisplayName) { + filc::AliasType type("char", std::make_shared("u8")); + ASSERT_STREQ("char", type.getDisplayName().c_str()); +} diff --git a/tests/unit/test_tools.cpp b/tests/unit/test_tools.cpp index 30de7f1..747351c 100644 --- a/tests/unit/test_tools.cpp +++ b/tests/unit/test_tools.cpp @@ -102,3 +102,42 @@ auto PrinterVisitor::visitAssignation(filc::Assignation *assignation) -> void { _out << assignation->getIdentifier() << " = "; assignation->getValue()->accept(this); } + +TokenSourceStub::TokenSourceStub(std::string filename) : _filename(std::move(filename)) {} + +auto TokenSourceStub::nextToken() -> std::unique_ptr { return nullptr; } + +auto TokenSourceStub::getLine() const -> size_t { return 0; } + +auto TokenSourceStub::getCharPositionInLine() -> size_t { return 0; } + +auto TokenSourceStub::getInputStream() -> antlr4::CharStream * { return nullptr; } + +auto TokenSourceStub::getSourceName() -> std::string { return _filename; } + +auto TokenSourceStub::getTokenFactory() -> antlr4::TokenFactory * { return nullptr; } + +TokenStub::TokenStub(const std::string &filename, const std::pair &position) + : _source(new TokenSourceStub(filename)), _position(position) {} + +auto TokenStub::getText() const -> std::string { return ""; } + +auto TokenStub::getType() const -> size_t { return 0; } + +auto TokenStub::getLine() const -> size_t { return _position.first; } + +auto TokenStub::getCharPositionInLine() const -> size_t { return _position.second; } + +auto TokenStub::getChannel() const -> size_t { return 0; } + +auto TokenStub::getTokenIndex() const -> size_t { return 0; } + +auto TokenStub::getStartIndex() const -> size_t { return 0; } + +auto TokenStub::getStopIndex() const -> size_t { return 0; } + +auto TokenStub::getTokenSource() const -> antlr4::TokenSource * { return _source; } + +auto TokenStub::getInputStream() const -> antlr4::CharStream * { return nullptr; } + +auto TokenStub::toString() const -> std::string { return ""; } diff --git a/tests/unit/test_tools.h b/tests/unit/test_tools.h index cadbb3c..4b9f02b 100644 --- a/tests/unit/test_tools.h +++ b/tests/unit/test_tools.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -65,4 +66,57 @@ class PrinterVisitor final: public filc::Visitor { std::stringstream _out; }; +class TokenSourceStub final: public antlr4::TokenSource { + public: + explicit TokenSourceStub(std::string filename); + + auto nextToken() -> std::unique_ptr override; + + [[nodiscard]] auto getLine() const -> size_t override; + + auto getCharPositionInLine() -> size_t override; + + auto getInputStream() -> antlr4::CharStream * override; + + auto getSourceName() -> std::string override; + + auto getTokenFactory() -> antlr4::TokenFactory * override; + + private: + std::string _filename; +}; + +class TokenStub final: public antlr4::Token { + public: + TokenStub(const std::string& filename, const std::pair &position); + + ~TokenStub() override = default; + + [[nodiscard]] auto getText() const -> std::string override; + + [[nodiscard]] auto getType() const -> size_t override; + + [[nodiscard]] auto getLine() const -> size_t override; + + [[nodiscard]] auto getCharPositionInLine() const -> size_t override; + + [[nodiscard]] auto getChannel() const -> size_t override; + + [[nodiscard]] auto getTokenIndex() const -> size_t override; + + [[nodiscard]] auto getStartIndex() const -> size_t override; + + [[nodiscard]] auto getStopIndex() const -> size_t override; + + [[nodiscard]] auto getTokenSource() const -> antlr4::TokenSource * override; + + [[nodiscard]] auto getInputStream() const -> antlr4::CharStream * override; + + [[nodiscard]] auto toString() const -> std::string override; + + private: + antlr4::TokenSource* _source; + std::pair _position; +}; + #endif // FILC_TEST_TOOLS_H diff --git a/tests/unit/utils/MessageTest.cpp b/tests/unit/utils/MessageTest.cpp new file mode 100644 index 0000000..6f2263d --- /dev/null +++ b/tests/unit/utils/MessageTest.cpp @@ -0,0 +1,42 @@ +/** + * 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 "test_tools.h" +#include +#include + +#define FILENAME FIXTURES_PATH "/ipsum.txt" + +TEST(Message, write) { + filc::Message message(WARNING, "This is a warning message", + filc::Position(new TokenStub(FILENAME, {1, 7}), new TokenStub(FILENAME, {1, 7})), + WARNING_COLOR); + std::stringstream ss; + ss << message; + std::string dump(std::istreambuf_iterator(ss), {}); + ASSERT_STREQ("\x1B[1m\x1B[33m[WARNING] \x1B[0mThis is a warning message\n" + " --> " FILENAME ":1:7\n" + " 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pharetra volutpat fermentum.\n" + " | \x1B[33m^\x1B[0m\n", + dump.c_str()); +} 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/EnvironmentTest.cpp b/tests/unit/validation/EnvironmentTest.cpp new file mode 100644 index 0000000..5b74cdf --- /dev/null +++ b/tests/unit/validation/EnvironmentTest.cpp @@ -0,0 +1,69 @@ +/** + * 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 + +TEST(Environment, constructor) { + filc::Environment env; + ASSERT_TRUE(env.hasType("i8")); + ASSERT_TRUE(env.hasType("i8")); + ASSERT_TRUE(env.hasType("i16")); + ASSERT_TRUE(env.hasType("i32")); + ASSERT_TRUE(env.hasType("i64")); + ASSERT_TRUE(env.hasType("i128")); + ASSERT_TRUE(env.hasType("int")); + ASSERT_TRUE(env.hasType("u8")); + ASSERT_TRUE(env.hasType("u16")); + ASSERT_TRUE(env.hasType("u32")); + ASSERT_TRUE(env.hasType("u64")); + ASSERT_TRUE(env.hasType("u128")); + ASSERT_TRUE(env.hasType("uint")); + ASSERT_TRUE(env.hasType("f32")); + ASSERT_TRUE(env.hasType("f64")); + ASSERT_TRUE(env.hasType("bool")); + ASSERT_TRUE(env.hasType("char")); + ASSERT_TRUE(env.hasType("char*")); +} + +TEST(Environment, Type) { + filc::Environment env; + ASSERT_FALSE(env.hasType("custom")); + ASSERT_THROW(env.getType("custom"), std::logic_error); + env.addType(std::make_shared("custom")); + ASSERT_THROW(env.addType(std::make_shared("custom")), std::logic_error); + ASSERT_TRUE(env.hasType("custom")); + ASSERT_STREQ("custom", env.getType("custom")->getName().c_str()); +} + +TEST(Environment, Name) { + filc::Environment env; + ASSERT_FALSE(env.hasName("my_name")); + ASSERT_THROW(env.getName("my_name"), std::logic_error); + env.addName(filc::Name(false, "my_name", env.getType("i32"))); + ASSERT_THROW(env.addName(filc::Name(true, "my_name", env.getType("i64"))), std::logic_error); + ASSERT_TRUE(env.hasName("my_name")); + ASSERT_FALSE(env.getName("my_name").isConstant()); + ASSERT_STREQ("my_name", env.getName("my_name").getName().c_str()); + ASSERT_STREQ("i32", env.getName("my_name").getType()->getName().c_str()); +} diff --git a/tests/unit/validation/NameTest.cpp b/tests/unit/validation/NameTest.cpp new file mode 100644 index 0000000..cd54f4f --- /dev/null +++ b/tests/unit/validation/NameTest.cpp @@ -0,0 +1,39 @@ +/** + * 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 + +TEST(Name, defaultConstructor) { + filc::Name name; + ASSERT_TRUE(name.isConstant()); + ASSERT_STREQ("", name.getName().c_str()); + ASSERT_EQ(nullptr, name.getType()); +} + +TEST(Name, constructor) { + filc::Name name(false, "my_var", std::make_shared("i32")); + ASSERT_FALSE(name.isConstant()); + ASSERT_STREQ("my_var", name.getName().c_str()); + ASSERT_STREQ("i32", name.getType()->getName().c_str()); +} diff --git a/tests/unit/validation/ValidationContextTest.cpp b/tests/unit/validation/ValidationContextTest.cpp new file mode 100644 index 0000000..40b67df --- /dev/null +++ b/tests/unit/validation/ValidationContextTest.cpp @@ -0,0 +1,103 @@ +/** + * 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 + +using namespace ::testing; + +TEST(ValidationContext, stack_unstack) { + filc::ValidationContext context; + context.set("key", 3); + ASSERT_TRUE(context.has("key")); + context.stack(); + ASSERT_FALSE(context.has("key")); + context.unstack(); + ASSERT_TRUE(context.has("key")); + ASSERT_EQ(3, context.get("key")); +} + +TEST(ValidationContext, get_non_existing) { + filc::ValidationContext context; + ASSERT_FALSE(context.has("non-existing")); + ASSERT_THROW(context.get("non-existing"), std::logic_error); +} + +TEST(ValidationContext, get_set_scalar) { + filc::ValidationContext context; + context.set("int_value", 2); + ASSERT_TRUE(context.has("int_value")); + ASSERT_EQ(2, context.get("int_value")); +} + +TEST(ValidationContext, get_set_string) { + filc::ValidationContext context; + context.set("string_value", std::string("Hello")); + ASSERT_TRUE(context.has("string_value")); + ASSERT_STREQ("Hello", context.get("string_value").c_str()); +} + +typedef struct { + int _a; + std::string _b; + char _c[5]; +} SomeStructure; + +TEST(ValidationContext, get_set_structure) { + filc::ValidationContext context; + SomeStructure value = {2, "Hello World", {'a', 'b', 'c', 'd', 'e'}}; + context.set("struct_value", value); + ASSERT_TRUE(context.has("struct_value")); + auto found = context.get("struct_value"); + ASSERT_EQ(2, found._a); + ASSERT_STREQ("Hello World", found._b.c_str()); + ASSERT_THAT(found._c, ElementsAre('a', 'b', 'c', 'd', 'e')); +} + +class SomeClass { + public: + SomeClass() : _a(12), _b("foo") {} + + [[nodiscard]] auto equals(const SomeClass &other) const -> bool { return _a == other._a && _b == other._b; } + + private: + int _a; + std::string _b; +}; + +TEST(ValidationContext, get_set_object) { + filc::ValidationContext context; + SomeClass value; + context.set("object_value", value); + ASSERT_TRUE(context.has("object_value")); + ASSERT_TRUE(value.equals(context.get("object_value"))); +} + +TEST(ValidationContext, clear) { + filc::ValidationContext context; + context.set("key", "value"); + ASSERT_TRUE(context.has("key")); + context.clear(); + ASSERT_FALSE(context.has("key")); +} diff --git a/tests/unit/validation/ValidationVisitorTest.cpp b/tests/unit/validation/ValidationVisitorTest.cpp new file mode 100644 index 0000000..b3b2d01 --- /dev/null +++ b/tests/unit/validation/ValidationVisitorTest.cpp @@ -0,0 +1,262 @@ +/** + * 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 "test_tools.h" +#include +#include +#include +#include +#include +#include + +using namespace ::testing; + +#define VISITOR \ + std::stringstream ss; \ + filc::ValidationVisitor visitor(ss) + +#define VALIDATION_FIXTURES FIXTURES_PATH "/validation" + +TEST(ValidationVisitor, program_valid) { + VISITOR; + const auto program = parseString("0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); +} + +TEST(ValidationVisitor, program_invalid) { + VISITOR; + const auto program = parseString("'a'"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Expected type int aka i32 but got char")); + ASSERT_TRUE(visitor.hasError()); +} + +TEST(ValidationVisitor, boolean) { + VISITOR; + const auto program = parseString("true\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Boolean value not used")); + ASSERT_FALSE(visitor.hasError()); +} + +TEST(ValidationVisitor, integer) { + VISITOR; + const auto program = parseString("2\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Integer value not used")); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("int", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, float) { + VISITOR; + const auto program = parseString("3.14\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Float value not used")); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("f64", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, character) { + VISITOR; + const auto program = parseString("'a'\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Character value not used")); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("char", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, string) { + VISITOR; + const auto program = parseString("\"hello\"\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("String value not used")); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("char*", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, variable_alreadyDefined) { + VISITOR; + const auto program = parseString("val my_constant = 2\nval my_constant = 3"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("my_constant is already defined")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_STREQ("int", program->getExpressions()[0]->getType()->getDisplayName().c_str()); + ASSERT_EQ(nullptr, program->getExpressions()[1]->getType()); +} + +TEST(ValidationVisitor, variable_constantWithoutValue) { + VISITOR; + const auto program = parseString("val my_constant"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("When declaring a constant, you must provide it a value")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, variable_unknowType) { + VISITOR; + const auto program = parseString("var my_var: foo"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Unknown type: foo")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, variable_differentValueType) { + VISITOR; + const auto program = parseString("var my_var: i32 = 'a'"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Cannot assign value of type char aka u8 to a variable of type i32")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, variable_assignAliasType) { + VISITOR; + const auto program = parseString("var my_var: u8 = 'a'\n0"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("u8", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, variable_withoutTypeAndValue) { + VISITOR; + const auto program = parseString("var my_var"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("When declaring a variable, you must provide at least a type or a value")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, variable_valid) { + VISITOR; + const auto program = parseString("val foo: i32 = 45"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("i32", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, identifier_nonExisting) { + VISITOR; + const auto program = parseString("bar"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Unknown name, don't know what it refers to: bar")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, identifier_valid) { + VISITOR; + const auto program = parseString("val foo = 1\nfoo"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("int", program->getExpressions()[1]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, calcul_invalidLeft) { + VISITOR; + const auto program = parseString("(val foo) + 2"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("When declaring a constant, you must provide it a value")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, calcul_invalidRight) { + VISITOR; + const auto program = parseString("2 + (val foo)"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("When declaring a constant, you must provide it a value")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, calcul_invalid) { + VISITOR; + const auto program = parseString("'a' && 3"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("You cannot use operator && with char aka u8 and int aka i32")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, calcul_valid) { + VISITOR; + const auto program = parseString("2 + 2"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("int", program->getExpressions()[0]->getType()->getDisplayName().c_str()); +} + +TEST(ValidationVisitor, assignation_nonExisting) { + VISITOR; + const auto program = parseString("foo = 3"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Unknown name, don't know what it refers to: foo")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[0]->getType()); +} + +TEST(ValidationVisitor, assignation_constant) { + VISITOR; + const auto program = parseString("val foo = 3\nfoo = 4"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), HasSubstr("Cannot modify a constant")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[1]->getType()); +} + +TEST(ValidationVisitor, assignation_differentType) { + VISITOR; + const auto program = parseString("var foo = 3\nfoo = false"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), + HasSubstr("Cannot assign value of type bool to a variable of type int aka i32")); + ASSERT_TRUE(visitor.hasError()); + ASSERT_EQ(nullptr, program->getExpressions()[1]->getType()); +} + +TEST(ValidationVisitor, assignation_valid) { + VISITOR; + const auto program = parseString("var foo = 3\nfoo = 2"); + program->accept(&visitor); + ASSERT_THAT(std::string(std::istreambuf_iterator(ss), {}), IsEmpty()); + ASSERT_FALSE(visitor.hasError()); + ASSERT_STREQ("int", program->getExpressions()[1]->getType()->getDisplayName().c_str()); +}