Skip to content

Commit

Permalink
feat: implement type casting functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
xXenvy committed Aug 11, 2024
1 parent d2d4c1d commit 54b99cb
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/common/include/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class ZynkErrorType {
PanicError,
CLIError,
DuplicateDeclarationError,
TypeCastError
};

struct ZynkError : public std::runtime_error { // Consider using templates for type
Expand All @@ -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";
}
}
Expand Down
86 changes: 65 additions & 21 deletions src/execution/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ASTBase>& 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) {
Expand All @@ -107,37 +155,33 @@ std::string Evaluator::evaluateExpression(ASTBase* expression) {
};
case ASTType::ReadLine: {
return evaluateReadLine(static_cast<ASTReadLine*>(expression));
}
};
case ASTType::TypeCast: {
return evaluateTypeCast(static_cast<ASTTypeCast*>(expression));
};
case ASTType::BinaryOperation: {
const auto operation = static_cast<ASTBinaryOperation*>(expression);
int valueTypes[2] = {
static_cast<int>(typeChecker.determineType(operation->left.get())),
static_cast<int>(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<ASTBase>& 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);
Expand Down
3 changes: 3 additions & 0 deletions src/execution/include/evaluator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion src/parsing/include/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ enum class ASTType {
Value,
Variable,
BinaryOperation,
Condition
Condition,
TypeCast
};

enum class ASTValueType {
Expand Down Expand Up @@ -106,4 +107,11 @@ struct ASTReadLine : public ASTBase {
std::unique_ptr<ASTBase> out;
};

struct ASTTypeCast : public ASTBase {
ASTTypeCast(std::unique_ptr<ASTBase> value, const ASTValueType type)
: ASTBase(ASTType::TypeCast), value(std::move(value)), castType(type) {}
std::unique_ptr<ASTBase> value;
const ASTValueType castType;
};

#endif // AST_H
1 change: 1 addition & 0 deletions src/parsing/include/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Parser {
std::unique_ptr<ASTBase> parseVariableModify();
std::unique_ptr<ASTBase> parsePrint(bool newLine);
std::unique_ptr<ASTBase> parseRead();
std::unique_ptr<ASTBase> parseTypeCast(TokenType type);
std::unique_ptr<ASTBase> parseExpression(int priority);
std::unique_ptr<ASTBase> parsePrimaryExpression();
std::unique_ptr<ASTBase> parseIfStatement();
Expand Down
35 changes: 35 additions & 0 deletions src/parsing/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,20 @@ std::unique_ptr<ASTBase> Parser::parseExpression(int priority) {
std::unique_ptr<ASTBase> 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<ASTValue>(current.value, ASTValueType::Integer);
case TokenType::FLOAT:
if (isTypeCast) return parseTypeCast(TokenType::FLOAT);
return std::make_unique<ASTValue>(current.value, ASTValueType::Float);
case TokenType::STRING:
if (isTypeCast) return parseTypeCast(TokenType::STRING);
return std::make_unique<ASTValue>(current.value, ASTValueType::String);
case TokenType::BOOL:
if (isTypeCast) return parseTypeCast(TokenType::BOOL);
return std::make_unique<ASTValue>(current.value, ASTValueType::Bool);
case TokenType::NONE:
return std::make_unique<ASTValue>(current.value, ASTValueType::None);
Expand Down Expand Up @@ -242,6 +247,36 @@ std::unique_ptr<ASTBase> Parser::parseIfStatement() {
return condition;
}

std::unique_ptr<ASTBase> Parser::parseTypeCast(TokenType type) {
const size_t currentLine = currentToken().line;
consume({ TokenType::LBRACKET, "(", currentLine });
std::unique_ptr<ASTBase> 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.",
&currentLine
};
}
return std::make_unique<ASTTypeCast>(std::move(value), castType);
}

bool Parser::endOfFile() const {
return position > tokens.size() || tokens[position].type == TokenType::END_OF_FILE;
}
Expand Down
2 changes: 2 additions & 0 deletions src/typechecker/checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ TypeChecker::TypeChecker(RuntimeEnvironment& env) : env(env) {};

ASTValueType TypeChecker::determineType(ASTBase* expression) {
switch (expression->type) {
case ASTType::TypeCast:
return static_cast<ASTTypeCast*>(expression)->castType;
case ASTType::ReadLine:
return ASTValueType::String;
case ASTType::Value: {
Expand Down
82 changes: 82 additions & 0 deletions tests/test_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token> 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<Token> 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<Token> 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<Token> 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<Token> 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<Token> tokens = lexer.tokenize();

Parser parser(tokens);
auto program = parser.parse();
Evaluator evaluator;

EXPECT_THROW(evaluator.evaluate(program.get()), ZynkError);
}
Loading

0 comments on commit 54b99cb

Please sign in to comment.