Add more typechecking

This commit is contained in:
Aaro Koivukangas
2026-03-12 16:03:01 +02:00
parent da0fc52f2d
commit 74ffd83a2d
2 changed files with 118 additions and 1 deletions
+33
View File
@@ -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")
}
}
+85 -1
View File
@@ -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)
}
}