diff --git a/compiler/src/main/scala/TypeChecker.scala b/compiler/src/main/scala/TypeChecker.scala index aaf0dfe..61e5aa8 100644 --- a/compiler/src/main/scala/TypeChecker.scala +++ b/compiler/src/main/scala/TypeChecker.scala @@ -10,8 +10,30 @@ case class CBool() extends CType case class CUnit() extends CType case class CNone() extends CType +class CSymbol(val name: Identifier, val value: Expression, val valType: CType) { + override def equals(other: Any): Boolean = other match { + case that: CSymbol => if (that.name.name == this.name.name && that.value == this.value && that.valType == this.valType) then true else false + case _ => false + } + override def toString: String = + s"CSymbol($name, $value, $valType)" +} + + +class SymbolTable(var symbols: List[CSymbol], var childTable: Option[SymbolTable]) { + override def equals(other: Any): Boolean = other match { + case that: SymbolTable => if that.symbols.zip(this.symbols).forall((thatSymbol, thisSymbol) => thatSymbol == thisSymbol) then true else false + case _ => false + } +} + class TypeChecker() { + private var symTab = SymbolTable(List(), None) + + def getSymTable(): SymbolTable = + return symTab + def typecheck(ast: Expression): CType = return ast match { case that: Literal => { @@ -71,6 +93,17 @@ class TypeChecker() { case conditionType => throw new Exception(s"WhileDo condition not bool: $conditionType") } } + + case that: Declaration => { + symTab.symbols ::: List(new CSymbol(that.name, that.body, typecheck(that.body))) + CUnit() // var x = 1 shouldn't have a type (x can and will have a type) + } + + case that: Block => { + // that.expressions.map(expr => typecheck(expr)) + return CUnit() + } + // case _ => throw new Exception("NOT IMPLEMENTED") } } diff --git a/compiler/src/test/scala/TypeCheckerTests.scala b/compiler/src/test/scala/TypeCheckerTests.scala index a06695f..22753e2 100644 --- a/compiler/src/test/scala/TypeCheckerTests.scala +++ b/compiler/src/test/scala/TypeCheckerTests.scala @@ -5,6 +5,7 @@ import compiler.Tokenizer.TokenType as tt import compiler.Parser._ import compiler.TypeChecker._ + abstract class BaseTypeCheckerTests extends munit.FunSuite { def getTokens(code: String): Option[List[Token]] = val tokens = Tokenizer.tokenize(code) @@ -18,12 +19,21 @@ abstract class BaseTypeCheckerTests extends munit.FunSuite { def testTypeChecker(code: String): CType = val tokens = getTokens(code) - val ast = Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug())))).parseExpression() + val ast = Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug())))).parseExpression(topLevelOrBlock=true) val typechecker = TypeChecker() return typechecker.typecheck(ast) + + def testTypeCheckerClass(code: String): (CType, TypeChecker) = + val tokens = getTokens(code) + val ast = Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug())))).parseExpression(topLevelOrBlock=true) + val typechecker = TypeChecker() + val result = typechecker.typecheck(ast) + return (result, typechecker) + } + class TypeCheckerLiteralTests extends BaseTypeCheckerTests { test("should typecheck IntLiteral correctly") { List("1", "45910", "0").map(value => { @@ -47,6 +57,7 @@ class TypeCheckerLiteralTests extends BaseTypeCheckerTests { } } + class TypeCheckerBinaryOperatorTests extends BaseTypeCheckerTests { test("should typecheck BinaryOperator with simple operators") { List("1 + 1", "1580 + 0", "594 - 594", "5 * 5", "47 / 3", "2 % 5").map(value => { @@ -57,6 +68,7 @@ class TypeCheckerBinaryOperatorTests extends BaseTypeCheckerTests { } } + class TypeCheckerIfThenElseTests extends BaseTypeCheckerTests { test("should typecheck simple IfThenElse with bodies as IntLiterals") { val result = testTypeChecker("if true then 1 else 2") @@ -75,6 +87,7 @@ class TypeCheckerIfThenElseTests extends BaseTypeCheckerTests { } } + class TypeCheckerWhileDoTests extends BaseTypeCheckerTests { test("should typecheck WhileDo with correct condition") { val result = testTypeChecker("while true do 1") @@ -87,3 +100,74 @@ class TypeCheckerWhileDoTests extends BaseTypeCheckerTests { } } } + + +class TypeCheckerCSymbolTests extends BaseTypeCheckerTests { + test("should be equal when two symbols are the same") { + List( + List( + new CSymbol(Identifier("x"), Literal(1), CInt()), new CSymbol(Identifier("x"), Literal(1), CInt()), + ), + List( + new CSymbol(Identifier("moi"), Literal(1058), CInt()), new CSymbol(Identifier("moi"), Literal(1058), CInt()) + ) + ).map(twoSymbols => { + assertEquals(twoSymbols(0), twoSymbols(1)) + assert(twoSymbols(0) == twoSymbols(1)) + }) + } + test("should not be equal when two symbols are not the same") { + List( + List( + new CSymbol(Identifier("x"), Literal(1), CInt()), new CSymbol(Identifier("x"), Literal(1230489), CInt()), + ), + List( + new CSymbol(Identifier("moi"), Literal(1058), CInt()), new CSymbol(Identifier("heimitästäs"), Literal(1058), CInt()), + ), + List( + new CSymbol(Identifier("mööö"), Literal(1), CBool()), new CSymbol(Identifier("mööö"), Literal(1), CInt()), + ), + List( + new CSymbol(Identifier("mööö"), Literal(10), CInt()), new CSymbol(Identifier("möööööööööö"), Literal(1000000), CInt()), + ) + ).map(twoSymbols => { + assertNotEquals(twoSymbols(0), twoSymbols(1)) + assert(twoSymbols(0) != twoSymbols(1)) + }) + } +} + + +class TypeCheckerDeclarationTests extends BaseTypeCheckerTests { + test("should typecheck simple declaration") { + val result = testTypeChecker("var x = 1") + val expected = CUnit() + assertEquals(result, expected) + } + test("should add typechecked declaration to symTable (simple)") { + val ast = testParser("var x = 1").parseExpression(topLevelOrBlock = true) + val (resultType, ts) = testTypeCheckerClass("var x = 1") + val expectedType = CUnit() + + val resultTable = ts.getSymTable() + val expectedTable = ast match { + case Declaration(name, body) => SymbolTable(List(new CSymbol(name, body, CInt())), None) + case _ => fail("Parser failed to parse even though it should have") + } + assertEquals(resultType, expectedType) + assertEquals(resultTable, expectedTable) + } + test("should add typechecked declaration to symTable (BinaryOperation)") { + val ast = testParser("var x = 2*5").parseExpression(topLevelOrBlock = true) + val (resultType, ts) = testTypeCheckerClass("var x = 2*5") + val expectedType = CUnit() + + val resultTable = ts.getSymTable() + val expectedTable = ast match { + case Declaration(name, body) => SymbolTable(List(new CSymbol(name, body, CInt())), None) + case _ => fail("Parser failed to parse even though it should have") + } + assertEquals(resultType, expectedType) + assertEquals(resultTable, expectedTable) + } +}