Skip to content

Commit

Permalink
feat: implement type checker for strong typing
Browse files Browse the repository at this point in the history
  • Loading branch information
xXenvy committed Jul 29, 2024
1 parent 5d1f1cd commit b470fe0
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 46 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
gc/gc.cpp
gc/object.cpp
common/cli.cpp
typechecker/checker.cpp
)
set(Headers
parsing/include/lexer.hpp
Expand All @@ -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
)
Expand Down
4 changes: 2 additions & 2 deletions src/common/include/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ enum class ZynkErrorType {
SyntaxError,
RuntimeError,
UnknownError,
InvalidTypeError,
TypeError,
NotDefinedError,
FileOpenError,
ExpressionError,
Expand All @@ -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";
Expand Down
9 changes: 8 additions & 1 deletion src/execution/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <memory>
#include <cassert>

Evaluator::Evaluator() : typeChecker(env) {};

void Evaluator::evaluate(ASTBase* ast) {
assert(ast != nullptr && "Ast should not be nullptr");
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions src/execution/include/evaluator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 12 additions & 3 deletions src/parsing/include/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down Expand Up @@ -48,10 +55,10 @@ struct ASTPrint : public ASTBase {
};

struct ASTVariableDeclaration : public ASTBase {
ASTVariableDeclaration(const std::string& name, const std::string& type, std::unique_ptr<ASTBase> value)
ASTVariableDeclaration(const std::string& name, const ASTValueType type, std::unique_ptr<ASTBase> 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<ASTBase> value;
};

Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/parsing/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
22 changes: 15 additions & 7 deletions src/parsing/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,33 @@ std::unique_ptr<ASTBase> 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 });

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.",
&currentLine,
};
}
consume(varTypeToken);
consume({ TokenType::ASSIGN, "=", currentLine });
auto varDeclaration = std::make_unique<ASTVariableDeclaration>(
varName, varType, parseExpression(0)
Expand All @@ -107,7 +113,6 @@ std::unique_ptr<ASTBase> Parser::parseVariableModify() {
consume({ TokenType::ASSIGN, "=", current.line });
std::unique_ptr<ASTBase> newValue = parseExpression(0);
consume({ TokenType::SEMICOLON, ";", current.line });

return std::make_unique<ASTVariableModify>(current.value, std::move(newValue));
}

Expand Down Expand Up @@ -170,10 +175,13 @@ std::unique_ptr<ASTBase> Parser::parsePrimaryExpression() {

switch (current.type) {
case TokenType::INT:
return std::make_unique<ASTValue>(current.value, ASTValueType::Integer);
case TokenType::FLOAT:
return std::make_unique<ASTValue>(current.value, ASTValueType::Float);
case TokenType::STRING:
return std::make_unique<ASTValue>(current.value, ASTValueType::String);
case TokenType::BOOL:
return std::make_unique<ASTValue>(current.value);
return std::make_unique<ASTValue>(current.value, ASTValueType::Bool);
case TokenType::IDENTIFIER:
return std::make_unique<ASTVariable>(current.value);
default:
Expand Down
61 changes: 61 additions & 0 deletions src/typechecker/checker.cpp
Original file line number Diff line number Diff line change
@@ -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<ASTValue*>(expression)->type;
}
case ASTType::Variable: {
ASTVariable* var = static_cast<ASTVariable*>(expression);
ASTVariableDeclaration* declaration = env.getVariable(var->name);
return determineType(declaration->value.get());
}
case ASTType::BinaryOperation: {
ASTBinaryOperation* operation = static_cast<ASTBinaryOperation*>(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";
}
}
18 changes: 18 additions & 0 deletions src/typechecker/include/checker.hpp
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(Sources
test_block.cpp
test_gc.cpp
test_runtime.cpp
test_typechecker.cpp
)
set(GoogleTestVersion v1.15.0)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
26 changes: 13 additions & 13 deletions tests/test_lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token> tokens = lexer.tokenize();
size_t type_counter = 0;
Expand All @@ -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<Token> tokens = lexer.tokenize();
size_t type_counter = 0;
Expand All @@ -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<Token> 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<Token> tokens = lexer.tokenize();
size_t type_counter = 0;
Expand All @@ -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);
}

Expand Down
Loading

0 comments on commit b470fe0

Please sign in to comment.