From b470fe00003b2a8eda04e7f857a61c618249a979 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 29 Jul 2024 23:54:13 +0200 Subject: [PATCH] feat: implement type checker for strong typing --- src/CMakeLists.txt | 2 + src/common/include/errors.hpp | 4 +- src/execution/evaluator.cpp | 9 +- src/execution/include/evaluator.hpp | 4 + src/parsing/include/ast.hpp | 15 +- src/parsing/lexer.cpp | 2 +- src/parsing/parser.cpp | 22 ++- src/typechecker/checker.cpp | 61 +++++++++ src/typechecker/include/checker.hpp | 18 +++ tests/CMakeLists.txt | 1 + tests/test_evaluator.cpp | 2 +- tests/test_lexer.cpp | 26 ++-- tests/test_parser.cpp | 12 +- tests/test_runtime.cpp | 24 ++-- tests/test_typechecker.cpp | 204 ++++++++++++++++++++++++++++ 15 files changed, 360 insertions(+), 46 deletions(-) create mode 100644 src/typechecker/checker.cpp create mode 100644 src/typechecker/include/checker.hpp create mode 100644 tests/test_typechecker.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9020404..06e0e2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ gc/gc.cpp gc/object.cpp common/cli.cpp + typechecker/checker.cpp ) set(Headers parsing/include/lexer.hpp @@ -21,6 +22,7 @@ set(Headers gc/include/block.hpp gc/include/object.hpp + typechecker/include/checker.hpp common/include/cli.hpp common/include/errors.hpp ) diff --git a/src/common/include/errors.hpp b/src/common/include/errors.hpp index 3894c63..ad6a47e 100644 --- a/src/common/include/errors.hpp +++ b/src/common/include/errors.hpp @@ -9,7 +9,7 @@ enum class ZynkErrorType { SyntaxError, RuntimeError, UnknownError, - InvalidTypeError, + TypeError, NotDefinedError, FileOpenError, ExpressionError, @@ -35,7 +35,7 @@ struct ZynkError : public std::runtime_error { // Consider using templates for t switch (base_type) { case ZynkErrorType::SyntaxError: return "SyntaxError"; case ZynkErrorType::RuntimeError: return "RuntimeError"; - case ZynkErrorType::InvalidTypeError: return "InvalidTypeError"; + case ZynkErrorType::TypeError: return "InvalidTypeError"; case ZynkErrorType::FileOpenError: return "FileOpenError"; case ZynkErrorType::ExpressionError: return "ExpressionError"; case ZynkErrorType::PanicError: return "PanicError"; diff --git a/src/execution/evaluator.cpp b/src/execution/evaluator.cpp index caf3d7d..0192219 100644 --- a/src/execution/evaluator.cpp +++ b/src/execution/evaluator.cpp @@ -3,6 +3,7 @@ #include #include +Evaluator::Evaluator() : typeChecker(env) {}; void Evaluator::evaluate(ASTBase* ast) { assert(ast != nullptr && "Ast should not be nullptr"); @@ -58,12 +59,18 @@ void Evaluator::evaluatePrint(ASTPrint* print) { } void Evaluator::evaluateVariableDeclaration(ASTVariableDeclaration* declaration) { + typeChecker.checkType(declaration->type, declaration->value.get()); env.declareVariable(declaration->name, declaration); } void Evaluator::evaluateVariableModify(ASTVariableModify* variableModify) { ASTVariableDeclaration* declaration = env.getVariable(variableModify->name); - ASTValue* newValue = new ASTValue(evaluateExpression(variableModify->value.get())); + typeChecker.checkType(declaration->type, variableModify->value.get()); + + ASTValue* newValue = new ASTValue( + evaluateExpression(variableModify->value.get()), + declaration->type + ); // We need to calculate the new value of the variable already at this point. declaration->value.reset(newValue); } diff --git a/src/execution/include/evaluator.hpp b/src/execution/include/evaluator.hpp index a9d44f5..032bcf1 100644 --- a/src/execution/include/evaluator.hpp +++ b/src/execution/include/evaluator.hpp @@ -2,13 +2,17 @@ #define EVALUATOR_H #include "../../parsing/include/ast.hpp" +#include "../../typechecker/include/checker.hpp" #include "runtime.hpp" class Evaluator { public: + Evaluator(); + RuntimeEnvironment env; void evaluate(ASTBase* ast); private: + TypeChecker typeChecker; std::string evaluateExpression(ASTBase* expression); void evaluateVariableDeclaration(ASTVariableDeclaration* variable); void evaluateVariableModify(ASTVariableModify* variableModify); diff --git a/src/parsing/include/ast.hpp b/src/parsing/include/ast.hpp index a7f2e6f..eb3d618 100644 --- a/src/parsing/include/ast.hpp +++ b/src/parsing/include/ast.hpp @@ -17,6 +17,13 @@ enum class ASTType { BinaryOperation }; +enum class ASTValueType { + String, + Integer, + Float, + Bool +}; + struct ASTBase { const ASTType type; ASTBase(ASTType type) : type(type) {} @@ -48,10 +55,10 @@ struct ASTPrint : public ASTBase { }; struct ASTVariableDeclaration : public ASTBase { - ASTVariableDeclaration(const std::string& name, const std::string& type, std::unique_ptr value) + ASTVariableDeclaration(const std::string& name, const ASTValueType type, std::unique_ptr value) : ASTBase(ASTType::VariableDeclaration), name(name), type(type), value(std::move(value)) {} const std::string name; - const std::string type; + const ASTValueType type; std::unique_ptr value; }; @@ -63,8 +70,10 @@ struct ASTVariableModify : public ASTBase { }; struct ASTValue : public ASTBase { - ASTValue(const std::string& value) : ASTBase(ASTType::Value), value(value) {} + ASTValue(const std::string& value, const ASTValueType type) + : ASTBase(ASTType::Value), value(value), type(type) {} const std::string value; + const ASTValueType type; }; struct ASTVariable : public ASTBase { diff --git a/src/parsing/lexer.cpp b/src/parsing/lexer.cpp index f0e7b4d..35c7516 100644 --- a/src/parsing/lexer.cpp +++ b/src/parsing/lexer.cpp @@ -74,7 +74,7 @@ Token Lexer::identifier() { if (value == "true" || value == "false") return Token(TokenType::BOOL, value, line); if (value == "int") return Token(TokenType::INT, "int", line); if (value == "float") return Token(TokenType::FLOAT, "float", line); - if (value == "String") return Token(TokenType::STRING, "String", line); + if (value == "string") return Token(TokenType::STRING, "string", line); if (value == "bool") return Token(TokenType::BOOL, "bool", line); if (value == "var") return Token(TokenType::VARIABLE, value, line); return Token(TokenType::IDENTIFIER, value, line); diff --git a/src/parsing/parser.cpp b/src/parsing/parser.cpp index 93d2d28..7b68831 100644 --- a/src/parsing/parser.cpp +++ b/src/parsing/parser.cpp @@ -70,7 +70,7 @@ std::unique_ptr Parser::parseVariableDeclaration() { consume({ TokenType::VARIABLE, "var", currentLine }); const std::string varName = currentToken().value; - std::string varType; + ASTValueType varType; consume({ TokenType::IDENTIFIER, varName, currentLine }); consume({ TokenType::COLON, ":", currentLine }); @@ -78,19 +78,25 @@ std::unique_ptr Parser::parseVariableDeclaration() { const Token varTypeToken = currentToken(); switch (varTypeToken.type) { case TokenType::INT: + varType = ASTValueType::Integer; + break; case TokenType::FLOAT: + varType = ASTValueType::Float; + break; case TokenType::STRING: + varType = ASTValueType::String; + break; case TokenType::BOOL: - varType = varTypeToken.value; - consume(varTypeToken); + varType = ASTValueType::Bool; break; default: throw ZynkError{ - ZynkErrorType::InvalidTypeError, - "Expected: String, bool, float or int. Found: '" + varTypeToken.value + "' instead.", + ZynkErrorType::TypeError, + "Expected: string, bool, float or int. Found: '" + varTypeToken.value + "' instead.", ¤tLine, }; } + consume(varTypeToken); consume({ TokenType::ASSIGN, "=", currentLine }); auto varDeclaration = std::make_unique( varName, varType, parseExpression(0) @@ -107,7 +113,6 @@ std::unique_ptr Parser::parseVariableModify() { consume({ TokenType::ASSIGN, "=", current.line }); std::unique_ptr newValue = parseExpression(0); consume({ TokenType::SEMICOLON, ";", current.line }); - return std::make_unique(current.value, std::move(newValue)); } @@ -170,10 +175,13 @@ std::unique_ptr Parser::parsePrimaryExpression() { switch (current.type) { case TokenType::INT: + return std::make_unique(current.value, ASTValueType::Integer); case TokenType::FLOAT: + return std::make_unique(current.value, ASTValueType::Float); case TokenType::STRING: + return std::make_unique(current.value, ASTValueType::String); case TokenType::BOOL: - return std::make_unique(current.value); + return std::make_unique(current.value, ASTValueType::Bool); case TokenType::IDENTIFIER: return std::make_unique(current.value); default: diff --git a/src/typechecker/checker.cpp b/src/typechecker/checker.cpp new file mode 100644 index 0000000..5a5fcf0 --- /dev/null +++ b/src/typechecker/checker.cpp @@ -0,0 +1,61 @@ +#include "include/checker.hpp" +#include "../common/include/errors.hpp" + +TypeChecker::TypeChecker(RuntimeEnvironment& env) : env(env) {}; + +ASTValueType TypeChecker::determineType(ASTBase* expression) { + switch (expression->type) { + case ASTType::Value: { + return static_cast(expression)->type; + } + case ASTType::Variable: { + ASTVariable* var = static_cast(expression); + ASTVariableDeclaration* declaration = env.getVariable(var->name); + return determineType(declaration->value.get()); + } + case ASTType::BinaryOperation: { + ASTBinaryOperation* operation = static_cast(expression); + ASTValueType leftType = determineType(operation->left.get()); + ASTValueType rightType = determineType(operation->right.get()); + + if (!isNumber(leftType)) return leftType; + if (!isNumber(rightType)) return rightType; + if (leftType == ASTValueType::Float || rightType == ASTValueType::Float) { + return ASTValueType::Float; + } + return ASTValueType::Integer; + } + default: + throw ZynkError{ ZynkErrorType::TypeError, "Cannot determine type for given AST type." }; + } +} + +void TypeChecker::checkType(const ASTValueType& declared, ASTBase* value) { + ASTValueType valueType = determineType(value); + if (declared != valueType) { + throw ZynkError{ + ZynkErrorType::TypeError, + "Type mismatch. Declared type is " + typeToString(declared) + + ", but assigned value is of type " + typeToString(valueType) + "." + }; + } +} + +bool TypeChecker::isNumber(const ASTValueType& type) { + return type == ASTValueType::Float || type == ASTValueType::Integer; +} + +std::string TypeChecker::typeToString(const ASTValueType& type) { + switch (type) { + case ASTValueType::String: + return "string"; + case ASTValueType::Integer: + return "int"; + case ASTValueType::Float: + return "float"; + case ASTValueType::Bool: + return "bool"; + default: + return "Unknown Type"; + } +} \ No newline at end of file diff --git a/src/typechecker/include/checker.hpp b/src/typechecker/include/checker.hpp new file mode 100644 index 0000000..5ee527e --- /dev/null +++ b/src/typechecker/include/checker.hpp @@ -0,0 +1,18 @@ +#ifndef CHECKER_HPP +#define CHECKER_HPP + +#include "../../parsing/include/ast.hpp" +#include "../../execution/include/runtime.hpp" + +class TypeChecker { +public: + TypeChecker(RuntimeEnvironment& env); + void checkType(const ASTValueType& declared, ASTBase* value); + ASTValueType determineType(ASTBase* expression); +private: + RuntimeEnvironment& env; + std::string typeToString(const ASTValueType& type); + bool isNumber(const ASTValueType& type); +}; + +#endif // CHECKER_HPP \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7ddaf49..6ade379 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(Sources test_block.cpp test_gc.cpp test_runtime.cpp + test_typechecker.cpp ) set(GoogleTestVersion v1.15.0) diff --git a/tests/test_evaluator.cpp b/tests/test_evaluator.cpp index 1914470..3ef2bf2 100644 --- a/tests/test_evaluator.cpp +++ b/tests/test_evaluator.cpp @@ -52,7 +52,7 @@ TEST(EvaluatorTest, EvaluateFunctionDeclaration) { TEST(EvaluatorTest, EvaluateFunctionCall) { const std::string code = R"( def myFunction(){ - var x: String = "Inside function."; + var x: string = "Inside function."; println(x); } myFunction(); diff --git a/tests/test_lexer.cpp b/tests/test_lexer.cpp index 0a93a4b..e183c64 100644 --- a/tests/test_lexer.cpp +++ b/tests/test_lexer.cpp @@ -70,7 +70,7 @@ TEST(LexerTokenizeTest, MultipleFunctionDefinitions) { } TEST(LexerTokenizeTest, IntVariableDefinitions) { - const std::string source = "a: int = 10;\nb: int = 01;\nc: int = 321321321323232;"; + const std::string source = "var a: int = 10;\nvar b: int = 01;\nvar c: int = 321321321323232;"; Lexer lexer(source); const std::vector tokens = lexer.tokenize(); size_t type_counter = 0; @@ -81,14 +81,14 @@ TEST(LexerTokenizeTest, IntVariableDefinitions) { type_counter++; } } - EXPECT_TRUE(tokens.size() == 19); + EXPECT_TRUE(tokens.size() == 22); EXPECT_TRUE(type_counter == 3 && tokens.back().line == 3); - EXPECT_TRUE(tokens.front().type == TokenType::IDENTIFIER); + EXPECT_TRUE(tokens.front().type == TokenType::VARIABLE); EXPECT_TRUE(tokens.back().type == TokenType::END_OF_FILE); } TEST(LexerTokenizeTest, FloatVariableDefinitions) { - const std::string source = "a: float = 10.0;\nb: float = 0.1;\nc: float = 32132132.1323232;"; + const std::string source = "var a: float = 10.0;\nvar b: float = 0.1;\nvar c: float = 32132132.1323232;"; Lexer lexer(source); const std::vector tokens = lexer.tokenize(); size_t type_counter = 0; @@ -99,32 +99,32 @@ TEST(LexerTokenizeTest, FloatVariableDefinitions) { type_counter++; } } - EXPECT_TRUE(tokens.size() == 19); + EXPECT_TRUE(tokens.size() == 22); EXPECT_TRUE(type_counter == 3); - EXPECT_TRUE(tokens.front().type == TokenType::IDENTIFIER); + EXPECT_TRUE(tokens.front().type == TokenType::VARIABLE); EXPECT_TRUE(tokens.back().type == TokenType::END_OF_FILE); } TEST(LexerTokenizeTest, StringVariableDefinitions) { - const std::string source = "a: String = \"Test\";\nb: String = \"AB123#@\";\n"; + const std::string source = "var a: string = \"Test\";\nvar b: string = \"AB123#@\";\n"; Lexer lexer(source); const std::vector tokens = lexer.tokenize(); size_t type_counter = 0; for (const Token& token : tokens) { - if (token.value == "String") { + if (token.value == "string") { EXPECT_TRUE(token.type == TokenType::STRING); type_counter++; } } - EXPECT_TRUE(tokens.size() == 13); + EXPECT_TRUE(tokens.size() == 15); EXPECT_TRUE(type_counter == 2); - EXPECT_TRUE(tokens.front().type == TokenType::IDENTIFIER); + EXPECT_TRUE(tokens.front().type == TokenType::VARIABLE); EXPECT_TRUE(tokens.back().type == TokenType::END_OF_FILE); } TEST(LexerTokenizeTest, BoolVariableDefinitions) { - const std::string source = "a: bool = false;\nb: bool = true;\nc: bool = false;"; + const std::string source = "var a: bool = false;\nvar b: bool = true;\nvar c: bool = false;"; Lexer lexer(source); const std::vector tokens = lexer.tokenize(); size_t type_counter = 0; @@ -135,9 +135,9 @@ TEST(LexerTokenizeTest, BoolVariableDefinitions) { type_counter++; } } - EXPECT_TRUE(tokens.size() == 19); + EXPECT_TRUE(tokens.size() == 22); EXPECT_TRUE(type_counter == 3); - EXPECT_TRUE(tokens.front().type == TokenType::IDENTIFIER); + EXPECT_TRUE(tokens.front().type == TokenType::VARIABLE); EXPECT_TRUE(tokens.back().type == TokenType::END_OF_FILE); } diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp index 1cd2018..47f0bc1 100644 --- a/tests/test_parser.cpp +++ b/tests/test_parser.cpp @@ -17,7 +17,7 @@ TEST(ParserTest, parseVariableDeclaration) { const auto var = static_cast(program->body.front().get()); - ASSERT_EQ(var->type, "int"); + ASSERT_EQ(var->type, ASTValueType::Integer); ASSERT_EQ(var->name, "a"); const auto value = static_cast(var->value.get()); ASSERT_NE(value, nullptr); @@ -26,7 +26,7 @@ TEST(ParserTest, parseVariableDeclaration) { TEST(ParserTest, parseMultipleVariableDeclarations) { Lexer lexer("var a: int = 1;\nvar b: float = 1.0;\n" - "var c: bool = true; \nvar d: String = \"Test\";"); + "var c: bool = true; \nvar d: string = \"Test\";"); const std::vector tokens = lexer.tokenize(); Parser parser(tokens); @@ -93,7 +93,7 @@ TEST(ParserTest, parseVariableDeclarationWithBinaryOperation) { const auto var = static_cast(program->body.front().get()); ASSERT_EQ(var->name, "a"); - ASSERT_EQ(var->type, "float"); + ASSERT_EQ(var->type, ASTValueType::Float); const auto operation = static_cast(var->value.get()); ASSERT_NE(operation, nullptr); @@ -120,7 +120,7 @@ TEST(ParserTest, parseVariableDeclarationWithComplexExpression) { const auto var = static_cast(program->body.front().get()); ASSERT_EQ(var->name, "a"); - ASSERT_EQ(var->type, "float"); + ASSERT_EQ(var->type, ASTValueType::Float); const auto operation = static_cast(var->value.get()); ASSERT_NE(operation, nullptr); @@ -243,7 +243,7 @@ TEST(ParserTest, ShouldThrowInvalidTypeError) { FAIL() << "Expected ZynkError thrown."; } catch (const ZynkError& error) { - ASSERT_EQ(error.base_type, ZynkErrorType::InvalidTypeError); + ASSERT_EQ(error.base_type, ZynkErrorType::TypeError); } catch (const std::exception& error) { FAIL() << "Unexpected exception type: " << error.what(); @@ -336,7 +336,7 @@ TEST(ParserTest, parseExpressionWithInvalidOperator) { } TEST(ParserTest, parseStringWithMissingClosingQuote) { - Lexer lexer("var a: String = \"Unclosed string;"); + Lexer lexer("var a: string = \"Unclosed string;"); const std::vector tokens = lexer.tokenize(); Parser parser(tokens); diff --git a/tests/test_runtime.cpp b/tests/test_runtime.cpp index f53373a..38d0792 100644 --- a/tests/test_runtime.cpp +++ b/tests/test_runtime.cpp @@ -8,8 +8,8 @@ TEST(RuntimeEnvironmentTest, VariableDeclaration) { RuntimeEnvironment env; env.enterNewBlock(); - auto varValue = std::make_unique("10"); - auto varDeclaration = std::make_unique("x", "int", std::move(varValue)); + auto varValue = std::make_unique("10", ASTValueType::Integer); + auto varDeclaration = std::make_unique("x", ASTValueType::Integer, std::move(varValue)); ASSERT_NO_THROW(env.declareVariable("x", varDeclaration.get())); auto retrievedVar = env.getVariable("x"); @@ -61,8 +61,8 @@ TEST(RuntimeEnvironmentTest, EnterAndExitBlock) { env.enterNewBlock(); ASSERT_NE(env.currentBlock(), nullptr); - auto varValue = std::make_unique("1.5"); - auto varDeclaration = std::make_unique("x", "float", std::move(varValue)); + auto varValue = std::make_unique("1.5", ASTValueType::Float); + auto varDeclaration = std::make_unique("x", ASTValueType::Float, std::move(varValue)); env.declareVariable("x", varDeclaration.get()); ASSERT_EQ(env.isVariableDeclared("x"), true); @@ -81,11 +81,11 @@ TEST(RuntimeEnvironmentTest, GarbageCollectionAfterBlockExit) { RuntimeEnvironment env; env.enterNewBlock(); - auto varValue1 = std::make_unique("1.5"); - auto varDeclaration1 = std::make_unique("x", "float", std::move(varValue1)); + auto varValue1 = std::make_unique("1.5", ASTValueType::Float); + auto varDeclaration1 = std::make_unique("x", ASTValueType::Float, std::move(varValue1)); - auto varValue2 = std::make_unique("5"); - auto varDeclaration2 = std::make_unique("z", "int", std::move(varValue2)); + auto varValue2 = std::make_unique("5", ASTValueType::Integer); + auto varDeclaration2 = std::make_unique("z", ASTValueType::Integer, std::move(varValue2)); env.declareVariable("var1", varDeclaration1.get()); env.declareVariable("var2", varDeclaration2.get()); @@ -99,8 +99,8 @@ TEST(RuntimeEnvironmentTest, GarbageCollectionWithNestedBlocks) { RuntimeEnvironment env; env.enterNewBlock(); - auto varValue = std::make_unique("Abc"); - auto globalVar = std::make_unique("globalVar", "String", std::move(varValue)); + auto varValue = std::make_unique("Abc", ASTValueType::String); + auto globalVar = std::make_unique("globalVar", ASTValueType::String, std::move(varValue)); auto globalFunc = std::make_unique("globalFunc"); ASSERT_NO_THROW(env.declareVariable("globalVar", globalVar.get())); @@ -111,8 +111,8 @@ TEST(RuntimeEnvironmentTest, GarbageCollectionWithNestedBlocks) { ASSERT_TRUE(env.isFunctionDeclared("globalFunc")); env.enterNewBlock(); - auto innerVarValue = std::make_unique("Cba"); - auto innerVar = std::make_unique("innerVar", "String", std::move(varValue)); + auto innerVarValue = std::make_unique("Cba", ASTValueType::String); + auto innerVar = std::make_unique("innerVar", ASTValueType::String, std::move(varValue)); auto innerFunc = std::make_unique("innerFunc"); env.declareVariable("innerVar", innerVar.get()); diff --git a/tests/test_typechecker.cpp b/tests/test_typechecker.cpp new file mode 100644 index 0000000..342e56c --- /dev/null +++ b/tests/test_typechecker.cpp @@ -0,0 +1,204 @@ +#include + +#include "../src/typechecker/include/checker.hpp" +#include "../src/common/include/errors.hpp" + +TEST(TypeCheckerTest, DetermineTypeIntegerValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeInt = std::make_unique("42", ASTValueType::Integer); + ASSERT_EQ(typeChecker.determineType(valueNodeInt.get()), ASTValueType::Integer); +} + +TEST(TypeCheckerTest, DetermineTypeFloatValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeFloat = std::make_unique("3.14", ASTValueType::Float); + ASSERT_EQ(typeChecker.determineType(valueNodeFloat.get()), ASTValueType::Float); +} + +TEST(TypeCheckerTest, DetermineTypeStringValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeString = std::make_unique("hello", ASTValueType::String); + ASSERT_EQ(typeChecker.determineType(valueNodeString.get()), ASTValueType::String); +} + +TEST(TypeCheckerTest, DetermineTypeBooleanValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeBool = std::make_unique("true", ASTValueType::Bool); + ASSERT_EQ(typeChecker.determineType(valueNodeBool.get()), ASTValueType::Bool); +} + +TEST(TypeCheckerTest, DetermineTypeZeroIntegerValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeZeroInt = std::make_unique("0", ASTValueType::Integer); + ASSERT_EQ(typeChecker.determineType(valueNodeZeroInt.get()), ASTValueType::Integer); +} + +TEST(TypeCheckerTest, DetermineTypeZeroFloatValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeZeroFloat = std::make_unique("0.0", ASTValueType::Float); + ASSERT_EQ(typeChecker.determineType(valueNodeZeroFloat.get()), ASTValueType::Float); +} + +TEST(TypeCheckerTest, DetermineTypeEmptyStringValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeEmptyString = std::make_unique("", ASTValueType::String); + ASSERT_EQ(typeChecker.determineType(valueNodeEmptyString.get()), ASTValueType::String); +} + +TEST(TypeCheckerTest, DetermineTypeFalseBooleanValueNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeFalseBool = std::make_unique("false", ASTValueType::Bool); + ASSERT_EQ(typeChecker.determineType(valueNodeFalseBool.get()), ASTValueType::Bool); +} + +TEST(TypeCheckerTest, DetermineTypeIntegerVariableNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + env.enterNewBlock(); + + auto varValue = std::make_unique("42", ASTValueType::Integer); + auto varDeclaration = std::make_unique("x", ASTValueType::Integer, std::move(varValue)); + env.declareVariable("x", varDeclaration.get()); + + auto variableNode = std::make_unique("x"); + ASSERT_EQ(typeChecker.determineType(variableNode.get()), ASTValueType::Integer); + env.exitCurrentBlock(); +} + +TEST(TypeCheckerTest, DetermineTypeFloatVariableNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + env.enterNewBlock(); + + auto varValue = std::make_unique("3.14", ASTValueType::Float); + auto varDeclaration = std::make_unique("y", ASTValueType::Float, std::move(varValue)); + env.declareVariable("y", varDeclaration.get()); + + auto variableNode = std::make_unique("y"); + ASSERT_EQ(typeChecker.determineType(variableNode.get()), ASTValueType::Float); + env.exitCurrentBlock(); +} + +TEST(TypeCheckerTest, DetermineTypeStringVariableNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + env.enterNewBlock(); + + auto varValue = std::make_unique("Hello", ASTValueType::String); + auto varDeclaration = std::make_unique("greeting", ASTValueType::String, std::move(varValue)); + env.declareVariable("greeting", varDeclaration.get()); + + auto variableNode = std::make_unique("greeting"); + ASSERT_EQ(typeChecker.determineType(variableNode.get()), ASTValueType::String); + env.exitCurrentBlock(); +} + +TEST(TypeCheckerTest, DetermineTypeUndeclaredVariableNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + env.enterNewBlock(); + + auto variableNode = std::make_unique("undeclaredVariable"); + ASSERT_THROW(typeChecker.determineType(variableNode.get()), ZynkError); + env.exitCurrentBlock(); +} + +TEST(TypeCheckerTest, DetermineTypeIntegerBinaryOperationNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto leftValue = std::make_unique("5", ASTValueType::Integer); + auto rightValue = std::make_unique("3", ASTValueType::Integer); + auto operationNode = std::make_unique( + std::move(leftValue), "+", std::move(rightValue)); + ASSERT_EQ(typeChecker.determineType(operationNode.get()), ASTValueType::Integer); +} + +TEST(TypeCheckerTest, DetermineTypeFloatBinaryOperationNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto leftValue = std::make_unique("5", ASTValueType::Integer); + auto rightValue = std::make_unique("3.14", ASTValueType::Float); + auto operationNode = std::make_unique( + std::move(leftValue), "*", std::move(rightValue)); + ASSERT_EQ(typeChecker.determineType(operationNode.get()), ASTValueType::Float); +} + +TEST(TypeCheckerTest, DetermineTypeMismatchedBinaryOperationNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto leftValue = std::make_unique("Hello", ASTValueType::String); + auto rightValue = std::make_unique("3.14", ASTValueType::Float); + auto operationNode = std::make_unique( + std::move(leftValue), "+", std::move(rightValue)); + + ASSERT_EQ(typeChecker.determineType(operationNode.get()), ASTValueType::String); +} + +TEST(TypeCheckerTest, CheckType_MatchingTypes) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNode = std::make_unique("true", ASTValueType::Bool); + ASSERT_NO_THROW(typeChecker.checkType(ASTValueType::Bool, valueNode.get())); +} + +TEST(TypeCheckerTest, CheckTypeMismatchedTypes) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNode = std::make_unique("42", ASTValueType::Integer); + ASSERT_THROW(typeChecker.checkType(ASTValueType::Float, valueNode.get()), ZynkError); +} + +TEST(TypeCheckerTest, CheckTypeBinaryOperation_MatchingTypes) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto leftValue = std::make_unique("5", ASTValueType::Integer); + auto rightValue = std::make_unique("3", ASTValueType::Integer); + auto operationNode = std::make_unique( + std::move(leftValue), "+", std::move(rightValue)); + + ASSERT_NO_THROW(typeChecker.checkType(ASTValueType::Integer, operationNode.get())); +} + +TEST(TypeCheckerTest, CheckType_UndeclaredVariableNode) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + env.enterNewBlock(); + + auto variableNode = std::make_unique("undeclaredVariable"); + ASSERT_THROW(typeChecker.checkType(ASTValueType::Integer, variableNode.get()), ZynkError); + env.exitCurrentBlock(); +} + +TEST(TypeCheckerTest, CheckType_MixedTypesInBinaryOperation) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto leftValue = std::make_unique("5", ASTValueType::Integer); + auto rightValue = std::make_unique("true", ASTValueType::Bool); + auto operationNode = std::make_unique( + std::move(leftValue), "+", std::move(rightValue)); + + ASSERT_THROW(typeChecker.checkType(ASTValueType::Integer, operationNode.get()), ZynkError); +} \ No newline at end of file