mirror of
https://github.com/iritmaximus/compilers.git
synced 2026-10-12 19:13:35 +03:00
Add typechecking
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package compiler.Interpreter
|
||||
|
||||
import compiler.Parser._
|
||||
import compiler.Tokenizer._
|
||||
|
||||
type Value = Option[Int | Boolean]
|
||||
|
||||
// TODO: Let's do this later...
|
||||
class Interpreter() {
|
||||
def interpret(expr: Expression): Value =
|
||||
return expr match {
|
||||
case that: BinaryOperator => {
|
||||
val left = interpret(that.left).get
|
||||
val right = interpret(that.right).get
|
||||
|
||||
that.operator match {
|
||||
// case "+" => left + right
|
||||
case _ => throw new Exception("NOT IMPLEMENTED")
|
||||
}
|
||||
}
|
||||
case that: Literal => {
|
||||
return that.value match {
|
||||
case value: Int => Some(value)
|
||||
case value: Unit => throw new Exception("NOT IMPLEMENTED")
|
||||
}
|
||||
}
|
||||
case that: IfThenElse => throw new Exception("NOT IMPLEMENTED")
|
||||
case _ => throw new Exception(s"Couldn't match expression type, got: $expr")
|
||||
}
|
||||
}
|
||||
|
||||
object Interpreter:
|
||||
def interpret(ast: Expression): Value =
|
||||
val interpreter = Interpreter()
|
||||
return interpreter.interpret(ast)
|
||||
@@ -218,6 +218,7 @@ class Parser(tokens: List[Token]):
|
||||
if peek().value == ";" then consume(Some(";")).get
|
||||
return Block(expressions)
|
||||
|
||||
|
||||
def parseExpression(initialDepth: Int = 0, topLevelOrBlock: Boolean = false): Expression =
|
||||
var depth = initialDepth
|
||||
var left = parseFactor()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package compiler.TypeChecker
|
||||
|
||||
import compiler.Parser._
|
||||
|
||||
|
||||
abstract class CType()
|
||||
|
||||
case class CInt() extends CType
|
||||
case class CBool() extends CType
|
||||
case class CUnit() extends CType
|
||||
case class CNone() extends CType
|
||||
|
||||
|
||||
class TypeChecker() {
|
||||
def typecheck(ast: Expression): CType =
|
||||
return ast match {
|
||||
case that: Literal => {
|
||||
that.value match {
|
||||
case _: Int => CInt()
|
||||
case _: Unit => CUnit()
|
||||
}
|
||||
}
|
||||
|
||||
case that: BinaryOperator => {
|
||||
val leftType = typecheck(that.left)
|
||||
val rightType = typecheck(that.right)
|
||||
|
||||
return that.operator match {
|
||||
case "+" | "-" | "*" | "%" | "/" => {
|
||||
(leftType, rightType) match {
|
||||
case (_: CInt, _: CInt) => CInt()
|
||||
case _ => throw new Exception(s"Incorrect left or right value in BinaryOperator l: $leftType r: $rightType, expected CInt")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case that: IfThenElse => {
|
||||
typecheck(that.condition) match {
|
||||
case _: CBool => {}
|
||||
case conditionType => throw new Exception(s"IfThenElse condition not bool: $conditionType")
|
||||
}
|
||||
|
||||
val ifBranchType = typecheck(that.body)
|
||||
val elseBranchType = that.elseBody match {
|
||||
case None => CNone()
|
||||
case Some(body) => typecheck(body)
|
||||
}
|
||||
|
||||
// TODO: var x = if x>1 then 1 could have type CNone()
|
||||
if elseBranchType == CNone() then
|
||||
return ifBranchType
|
||||
|
||||
if ifBranchType != elseBranchType then
|
||||
throw new Exception(s"IfThenElse branches don't share a type: $ifBranchType vs $elseBranchType")
|
||||
|
||||
return ifBranchType
|
||||
}
|
||||
|
||||
case that: Identifier => {
|
||||
return that.name match {
|
||||
case "true" | "false" => CBool()
|
||||
case _ => throw new Exception("NOT IMPLEMENTED")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case that: WhileDo => {
|
||||
// NOTE: Should While do return only CUNit?
|
||||
typecheck(that.condition) match {
|
||||
case _: CBool => CUnit()
|
||||
case conditionType => throw new Exception(s"WhileDo condition not bool: $conditionType")
|
||||
}
|
||||
}
|
||||
// case _ => throw new Exception("NOT IMPLEMENTED")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object TypeChecker:
|
||||
def typecheck(ast: Expression): CType =
|
||||
val ts = TypeChecker()
|
||||
ts.typecheck(ast)
|
||||
@@ -0,0 +1,32 @@
|
||||
import compiler.Tokenizer._
|
||||
import compiler.Tokenizer.TokenType as tt
|
||||
import compiler.Parser._
|
||||
import compiler.Interpreter._
|
||||
import scala.util.{Try, Failure, Success}
|
||||
|
||||
abstract class BaseInterpreterTests extends munit.FunSuite {
|
||||
def getTokens(code: String): Option[List[Token]] =
|
||||
val tokens = Tokenizer.tokenize(code)
|
||||
return tokens match {
|
||||
case Success(tokens) => Some(tokens)
|
||||
case Failure(tokens) => fail("Tokenizing failed: " + tokens)
|
||||
}
|
||||
|
||||
def testParser(code: String): Parser =
|
||||
val tokens = getTokens(code)
|
||||
return Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug()))))
|
||||
|
||||
def testInterpreter(code: String) =
|
||||
val parser = testParser(code)
|
||||
val ast = parser.parseExpression()
|
||||
Interpreter.interpret(ast)
|
||||
}
|
||||
|
||||
class InterpreterLiteralTests extends BaseInterpreterTests {
|
||||
test("should interpret 1 correctly") {
|
||||
val result = testInterpreter("1").get
|
||||
val expected = 1
|
||||
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
}
|
||||
@@ -535,6 +535,17 @@ class ParserUnaryOperatorTests extends BaseParserTests {
|
||||
None
|
||||
)
|
||||
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
test("should parse unary `-` operator in BinaryOperator") {
|
||||
val p = testParser("1--2")
|
||||
val result = p.parseExpression()
|
||||
val expected = BinaryOperator(
|
||||
Literal(1),
|
||||
"-",
|
||||
UnaryOperator("-", Literal(2))
|
||||
)
|
||||
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import scala.util.{Try, Failure, Success}
|
||||
|
||||
import compiler.Tokenizer._
|
||||
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)
|
||||
return tokens match {
|
||||
case Success(tokens) => Some(tokens)
|
||||
case Failure(tokens) => fail("Tokenizing failed: " + tokens)
|
||||
}
|
||||
def testParser(code: String): Parser =
|
||||
val tokens = getTokens(code)
|
||||
return Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug()))))
|
||||
|
||||
def testTypeChecker(code: String): CType =
|
||||
val tokens = getTokens(code)
|
||||
val ast = Parser(tokens.getOrElse(List(Token("error", tt.Error, TokenLocationDebug())))).parseExpression()
|
||||
val typechecker = TypeChecker()
|
||||
return typechecker.typecheck(ast)
|
||||
|
||||
}
|
||||
|
||||
class TypeCheckerLiteralTests extends BaseTypeCheckerTests {
|
||||
test("should typecheck IntLiteral correctly") {
|
||||
List("1", "45910", "0").map(value => {
|
||||
val result = testTypeChecker(value)
|
||||
val expected = CInt()
|
||||
assertEquals(result, expected)
|
||||
})
|
||||
}
|
||||
test("should typecheck booleans") {
|
||||
List("true", "false").map(value => {
|
||||
val result = testTypeChecker(value)
|
||||
val expected = CBool()
|
||||
assertEquals(result, expected)
|
||||
})
|
||||
}
|
||||
// TODO
|
||||
test("should typecheck negative IntLiteral".fail) {
|
||||
val result = testTypeChecker("-14")
|
||||
val expected = CInt()
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
val result = testTypeChecker(value)
|
||||
val expected = CInt()
|
||||
assertEquals(result, expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class TypeCheckerIfThenElseTests extends BaseTypeCheckerTests {
|
||||
test("should typecheck simple IfThenElse with bodies as IntLiterals") {
|
||||
val result = testTypeChecker("if true then 1 else 2")
|
||||
val expected = CInt()
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
test("should typecheck simple IfThenElse with if-branch as IntLiteral and else-branch None") {
|
||||
val result = testTypeChecker("if true then 1")
|
||||
val expected = CInt()
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
test("should fail typecheck fi IfThenElse branches have different types") {
|
||||
interceptMessage[java.lang.Exception]("IfThenElse branches don't share a type: CInt() vs CBool()") {
|
||||
testTypeChecker("if true then 1 else false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TypeCheckerWhileDoTests extends BaseTypeCheckerTests {
|
||||
test("should typecheck WhileDo with correct condition") {
|
||||
val result = testTypeChecker("while true do 1")
|
||||
val expected = CUnit()
|
||||
assertEquals(result, expected)
|
||||
}
|
||||
test("should not typecheck WhileDo with incorrect condition") {
|
||||
interceptMessage[java.lang.Exception]("WhileDo condition not bool: CInt()") {
|
||||
testTypeChecker("while 4 do true")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user