diff --git a/src/common/include/errors.hpp b/src/common/include/errors.hpp index ad6a47e..0ddccbd 100644 --- a/src/common/include/errors.hpp +++ b/src/common/include/errors.hpp @@ -16,6 +16,7 @@ enum class ZynkErrorType { PanicError, CLIError, DuplicateDeclarationError, + TypeCastError }; struct ZynkError : public std::runtime_error { // Consider using templates for type @@ -42,6 +43,7 @@ struct ZynkError : public std::runtime_error { // Consider using templates for t case ZynkErrorType::CLIError: return "CLIError"; case ZynkErrorType::NotDefinedError: return "NotDefinedError"; case ZynkErrorType::DuplicateDeclarationError: return "DuplicateDeclarationError"; + case ZynkErrorType::TypeCastError: return "TypeCastError"; default: return "UnknownError"; } } diff --git a/src/execution/evaluator.cpp b/src/execution/evaluator.cpp index 4e64c5d..5790493 100644 --- a/src/execution/evaluator.cpp +++ b/src/execution/evaluator.cpp @@ -96,6 +96,54 @@ void Evaluator::evaluateVariableModify(ASTVariableModify* variableModify) { declaration->value.reset(newValue); } +std::string Evaluator::evaluateTypeCast(ASTTypeCast* typeCast) { + std::string base = evaluateExpression(typeCast->value.get()); + + switch (typeCast->castType) { + case ASTValueType::Integer: + try { + return std::to_string(std::stoi(base)); + } catch (const std::invalid_argument&) { + throw ZynkError{ + ZynkErrorType::TypeCastError, + "Invalid argument: unable to convert the provided value to an integer." + }; + } + case ASTValueType::Float: + try { + return std::to_string(std::stof(base)); + } catch (const std::invalid_argument&) { + throw ZynkError{ + ZynkErrorType::TypeCastError, + "Invalid argument. Unable to convert the provided value to an float." + }; + } + case ASTValueType::String: + return base; // Expressions by default are always strings. + case ASTValueType::Bool: { + if (base == "0" || base.empty() || base == "null" || base == "false") { + return "false"; + } + return "true"; + } + default: { + // This should never happen, but whatever + throw ZynkError{ ZynkErrorType::RuntimeError, "Invalid type cast." }; + } + } +} + +void Evaluator::evaluateCondition(ASTCondition* condition) { + const std::string value = evaluateExpression(condition->expression.get()); + bool isFalse = value == "0" || value.empty() || value == "null" || value == "false"; + + env.enterNewBlock(); + for (const std::unique_ptr& child : isFalse ? condition->elseBody : condition->body) { + evaluate(child.get()); + } + env.exitCurrentBlock(); +} + std::string Evaluator::evaluateExpression(ASTBase* expression) { if (expression == nullptr) return "null"; switch (expression->type) { @@ -107,37 +155,33 @@ std::string Evaluator::evaluateExpression(ASTBase* expression) { }; case ASTType::ReadLine: { return evaluateReadLine(static_cast(expression)); - } + }; + case ASTType::TypeCast: { + return evaluateTypeCast(static_cast(expression)); + }; case ASTType::BinaryOperation: { const auto operation = static_cast(expression); + int valueTypes[2] = { + static_cast(typeChecker.determineType(operation->left.get())), + static_cast(typeChecker.determineType(operation->right.get())) + }; + for (int valueType : valueTypes) { + if (valueType != 1 && valueType != 2) { + throw ZynkError{ + ZynkErrorType::ExpressionError, + "Invalid expression. Cannot perform BinaryOperation on that type." + }; + } + } const std::string left = evaluateExpression(operation->left.get()); const std::string right = evaluateExpression(operation->right.get()); - try { - return calculateString(left, right, operation->op); - } catch (const std::invalid_argument&) { - throw ZynkError{ - ZynkErrorType::ExpressionError, - "Invalid expression. Cannot perform BinaryOperation on that type." - }; - } + return calculateString(left, right, operation->op); }; default: throw ZynkError{ZynkErrorType::RuntimeError, "Invalid expression."}; } } -void Evaluator::evaluateCondition(ASTCondition* condition) { - const std::string value = evaluateExpression(condition->expression.get()); - bool result = true; - if (value == "0" || value.empty() || value == "null" || value == "false") result = false; - - env.enterNewBlock(); - for (const std::unique_ptr& child : result ? condition->body : condition->elseBody) { - evaluate(child.get()); - } - env.exitCurrentBlock(); -} - std::string calculate(const float left, const float right, const std::string& op) { if (op == "*") return std::to_string(left * right); if (op == "-") return std::to_string(left - right); diff --git a/src/execution/include/evaluator.hpp b/src/execution/include/evaluator.hpp index 54c9011..80e1822 100644 --- a/src/execution/include/evaluator.hpp +++ b/src/execution/include/evaluator.hpp @@ -13,8 +13,11 @@ class Evaluator { void evaluate(ASTBase* ast); private: TypeChecker typeChecker; + std::string evaluateExpression(ASTBase* expression); std::string evaluateReadLine(ASTReadLine* read); + std::string evaluateTypeCast(ASTTypeCast* typeCast); + void evaluateVariableDeclaration(ASTVariableDeclaration* variable); void evaluateVariableModify(ASTVariableModify* variableModify); void evaluateProgram(ASTProgram* program); diff --git a/src/parsing/include/ast.hpp b/src/parsing/include/ast.hpp index 0e737f7..2b9259d 100644 --- a/src/parsing/include/ast.hpp +++ b/src/parsing/include/ast.hpp @@ -16,7 +16,8 @@ enum class ASTType { Value, Variable, BinaryOperation, - Condition + Condition, + TypeCast }; enum class ASTValueType { @@ -106,4 +107,11 @@ struct ASTReadLine : public ASTBase { std::unique_ptr out; }; +struct ASTTypeCast : public ASTBase { + ASTTypeCast(std::unique_ptr value, const ASTValueType type) + : ASTBase(ASTType::TypeCast), value(std::move(value)), castType(type) {} + std::unique_ptr value; + const ASTValueType castType; +}; + #endif // AST_H diff --git a/src/parsing/include/parser.hpp b/src/parsing/include/parser.hpp index f858340..58c8b33 100644 --- a/src/parsing/include/parser.hpp +++ b/src/parsing/include/parser.hpp @@ -24,6 +24,7 @@ class Parser { std::unique_ptr parseVariableModify(); std::unique_ptr parsePrint(bool newLine); std::unique_ptr parseRead(); + std::unique_ptr parseTypeCast(TokenType type); std::unique_ptr parseExpression(int priority); std::unique_ptr parsePrimaryExpression(); std::unique_ptr parseIfStatement(); diff --git a/src/parsing/parser.cpp b/src/parsing/parser.cpp index 7c82f7b..2db1271 100644 --- a/src/parsing/parser.cpp +++ b/src/parsing/parser.cpp @@ -181,15 +181,20 @@ std::unique_ptr Parser::parseExpression(int priority) { std::unique_ptr Parser::parsePrimaryExpression() { const Token current = currentToken(); moveForward(); + const bool isTypeCast = currentToken().type == TokenType::LBRACKET; switch (current.type) { case TokenType::INT: + if (isTypeCast) return parseTypeCast(TokenType::INT); return std::make_unique(current.value, ASTValueType::Integer); case TokenType::FLOAT: + if (isTypeCast) return parseTypeCast(TokenType::FLOAT); return std::make_unique(current.value, ASTValueType::Float); case TokenType::STRING: + if (isTypeCast) return parseTypeCast(TokenType::STRING); return std::make_unique(current.value, ASTValueType::String); case TokenType::BOOL: + if (isTypeCast) return parseTypeCast(TokenType::BOOL); return std::make_unique(current.value, ASTValueType::Bool); case TokenType::NONE: return std::make_unique(current.value, ASTValueType::None); @@ -242,6 +247,36 @@ std::unique_ptr Parser::parseIfStatement() { return condition; } +std::unique_ptr Parser::parseTypeCast(TokenType type) { + const size_t currentLine = currentToken().line; + consume({ TokenType::LBRACKET, "(", currentLine }); + std::unique_ptr value = parseExpression(0); + consume({ TokenType::RBRACKET, ")", currentLine }); + + ASTValueType castType; + switch (type) { + case TokenType::INT: + castType = ASTValueType::Integer; + break; + case TokenType::FLOAT: + castType = ASTValueType::Float; + break; + case TokenType::STRING: + castType = ASTValueType::String; + break; + case TokenType::BOOL: + castType = ASTValueType::Bool; + break; + default: + throw ZynkError{ + ZynkErrorType::SyntaxError, + "Invalid type cast. Expected 'int', 'float', 'string', or 'bool', but found an unrecognized type.", + ¤tLine + }; + } + return std::make_unique(std::move(value), castType); +} + bool Parser::endOfFile() const { return position > tokens.size() || tokens[position].type == TokenType::END_OF_FILE; } diff --git a/src/typechecker/checker.cpp b/src/typechecker/checker.cpp index c49f2ad..64c51a0 100644 --- a/src/typechecker/checker.cpp +++ b/src/typechecker/checker.cpp @@ -5,6 +5,8 @@ TypeChecker::TypeChecker(RuntimeEnvironment& env) : env(env) {}; ASTValueType TypeChecker::determineType(ASTBase* expression) { switch (expression->type) { + case ASTType::TypeCast: + return static_cast(expression)->castType; case ASTType::ReadLine: return ASTValueType::String; case ASTType::Value: { diff --git a/tests/test_evaluator.cpp b/tests/test_evaluator.cpp index dcb088d..d791774 100644 --- a/tests/test_evaluator.cpp +++ b/tests/test_evaluator.cpp @@ -435,4 +435,86 @@ TEST(EvaluatorTest, EvaluateReadStatementWithoutPrompt) { std::cin.rdbuf(originalCinStreamBuf); std::string captured_output = testing::internal::GetCapturedStdout(); ASSERT_EQ(captured_output, "Bob\n"); +} + +TEST(EvaluatorTest, EvaluateStringToIntCast) { + const std::string code = "var x: int = int(\"42\");\nprintln(x + 1);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + const auto program = parser.parse(); + + testing::internal::CaptureStdout(); + Evaluator evaluator; + evaluator.evaluate(program.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "43\n"); +} + +TEST(EvaluatorTest, EvaluateIntToFloatCast) { + const std::string code = "var x: float = float(42);\nprintln(x);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + const auto program = parser.parse(); + + testing::internal::CaptureStdout(); + Evaluator evaluator; + evaluator.evaluate(program.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "42.000000\n"); +} + +TEST(EvaluatorTest, EvaluateFloatToIntCast) { + const std::string code = "var x: int = int(42.99);\nprintln(x);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + const auto program = parser.parse(); + + testing::internal::CaptureStdout(); + Evaluator evaluator; + evaluator.evaluate(program.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "42\n"); +} + +TEST(EvaluatorTest, EvaluateStringToFloatCast) { + const std::string code = "var x: float = float(\"42.50\");\nprintln(x);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + const auto program = parser.parse(); + + testing::internal::CaptureStdout(); + Evaluator evaluator; + evaluator.evaluate(program.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "42.500000\n"); +} + +TEST(EvaluatorTest, EvaluateStringToBoolCast) { + const std::string code = "var x: bool = bool(\"0\");\nprintln(x);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + const auto program = parser.parse(); + + testing::internal::CaptureStdout(); + Evaluator evaluator; + evaluator.evaluate(program.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "false\n"); +} + +TEST(EvaluatorTest, EvaluateInvalidStringToIntCast) { + const std::string code = "var x: int = int(\"not_a_number\");\nprintln(x);"; + Lexer lexer(code); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + auto program = parser.parse(); + Evaluator evaluator; + + EXPECT_THROW(evaluator.evaluate(program.get()), ZynkError); } \ No newline at end of file diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp index 0fbb839..6065d50 100644 --- a/tests/test_parser.cpp +++ b/tests/test_parser.cpp @@ -306,6 +306,102 @@ TEST(ParserTest, parseReadLineWithText) { ASSERT_EQ(text->value, "Enter your name: "); } +TEST(ParserTest, parseTypeCastFromStringToInt) { + Lexer lexer("var a: int = int(\"123\");"); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + auto program = parser.parse(); + + ASSERT_EQ(program->type, ASTType::Program); + ASSERT_EQ(program->body.size(), 1); + ASSERT_EQ(program->body.front()->type, ASTType::VariableDeclaration); + + const auto var = static_cast(program->body.front().get()); + ASSERT_EQ(var->name, "a"); + ASSERT_EQ(var->type, ASTValueType::Integer); + + const auto cast = static_cast(var->value.get()); + ASSERT_NE(cast, nullptr); + ASSERT_EQ(cast->castType, ASTValueType::Integer); + + const auto castValue = static_cast(cast->value.get()); + ASSERT_NE(castValue, nullptr); + ASSERT_EQ(castValue->value, "123"); +} + +TEST(ParserTest, parseTypeCastFromIntToString) { + Lexer lexer("var a: string = string(123);"); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + auto program = parser.parse(); + + ASSERT_EQ(program->type, ASTType::Program); + ASSERT_EQ(program->body.size(), 1); + ASSERT_EQ(program->body.front()->type, ASTType::VariableDeclaration); + + const auto var = static_cast(program->body.front().get()); + ASSERT_EQ(var->name, "a"); + ASSERT_EQ(var->type, ASTValueType::String); + + const auto cast = static_cast(var->value.get()); + ASSERT_NE(cast, nullptr); + ASSERT_EQ(cast->castType, ASTValueType::String); + + const auto castValue = static_cast(cast->value.get()); + ASSERT_NE(castValue, nullptr); + ASSERT_EQ(castValue->value, "123"); +} + +TEST(ParserTest, parseTypeCastFromStringToFloat) { + Lexer lexer("var a: float = float(\"123.45\");"); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + auto program = parser.parse(); + + ASSERT_EQ(program->type, ASTType::Program); + ASSERT_EQ(program->body.size(), 1); + ASSERT_EQ(program->body.front()->type, ASTType::VariableDeclaration); + + const auto var = static_cast(program->body.front().get()); + ASSERT_EQ(var->name, "a"); + ASSERT_EQ(var->type, ASTValueType::Float); + + const auto cast = static_cast(var->value.get()); + ASSERT_NE(cast, nullptr); + ASSERT_EQ(cast->castType, ASTValueType::Float); + + const auto castValue = static_cast(cast->value.get()); + ASSERT_NE(castValue, nullptr); + ASSERT_EQ(castValue->value, "123.45"); +} + +TEST(ParserTest, parseTypeCastFromBoolToString) { + Lexer lexer("var a: string = string(true);"); + const std::vector tokens = lexer.tokenize(); + + Parser parser(tokens); + auto program = parser.parse(); + + ASSERT_EQ(program->type, ASTType::Program); + ASSERT_EQ(program->body.size(), 1); + ASSERT_EQ(program->body.front()->type, ASTType::VariableDeclaration); + + const auto var = static_cast(program->body.front().get()); + ASSERT_EQ(var->name, "a"); + ASSERT_EQ(var->type, ASTValueType::String); + + const auto cast = static_cast(var->value.get()); + ASSERT_NE(cast, nullptr); + ASSERT_EQ(cast->castType, ASTValueType::String); + + const auto castValue = static_cast(cast->value.get()); + ASSERT_NE(castValue, nullptr); + ASSERT_EQ(castValue->value, "true"); +} + TEST(ParserTest, parseIfElseStatementWithSyntaxError) { Lexer lexer("if (a > b) { println(10) else { println(20); }"); // Missing semicolon const std::vector tokens = lexer.tokenize(); diff --git a/tests/test_typechecker.cpp b/tests/test_typechecker.cpp index 9da7076..6dd2549 100644 --- a/tests/test_typechecker.cpp +++ b/tests/test_typechecker.cpp @@ -201,4 +201,31 @@ TEST(TypeCheckerTest, CheckTypeMixedTypesInBinaryOperation) { std::move(leftValue), "+", std::move(rightValue)); ASSERT_THROW(typeChecker.checkType(ASTValueType::Integer, operationNode.get()), ZynkError); +} + +TEST(TypeCheckerTest, DetermineTypeCastFromFloatToInt) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeFloat = std::make_unique("3.14", ASTValueType::Float); + auto castNode = std::make_unique(std::move(valueNodeFloat), ASTValueType::Integer); + ASSERT_EQ(typeChecker.determineType(castNode.get()), ASTValueType::Integer); +} + +TEST(TypeCheckerTest, DetermineTypeCastFromStringToFloat) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeString = std::make_unique("3.14", ASTValueType::String); + auto castNode = std::make_unique(std::move(valueNodeString), ASTValueType::Float); + ASSERT_EQ(typeChecker.determineType(castNode.get()), ASTValueType::Float); +} + +TEST(TypeCheckerTest, DetermineTypeCastFromIntToBool) { + RuntimeEnvironment env; + TypeChecker typeChecker(env); + + auto valueNodeInt = std::make_unique("1", ASTValueType::Integer); + auto castNode = std::make_unique(std::move(valueNodeInt), ASTValueType::Bool); + ASSERT_EQ(typeChecker.determineType(castNode.get()), ASTValueType::Bool); } \ No newline at end of file