Change to use throws instead of Try

This commit is contained in:
Aaro Koivukangas
2026-02-27 17:09:51 +02:00
parent f8f222ded6
commit 3f618b7cb8
3 changed files with 68 additions and 124 deletions
+30 -43
View File
@@ -17,6 +17,9 @@ case class Identifier(name: String) extends Expression {
case class BinaryOperator(left: Expression, operator: String, right: Expression) extends Expression {
override def toString: String = s"BinaryOperator(l: $left, op: $operator, r: $right)"
}
// case class ControlFlow(name: String, condition: Expression, body: Expression, elseBody: Option[Expression]) {
// override def toString: String = s"ControlFlow($name $condition: $body)"
// }
case class Other() extends Expression {
override def toString: String = s"Other()"
}
@@ -27,13 +30,12 @@ class Parser(tokens: List[Token]):
def peek(): Token =
return if pos < tokens.length then tokens(pos) else Token("", TokenType.End, tokens.last.location)
return tokens(pos)
def consume(expected: Option[TokenType | String | List[String]] = None): Try[Token] =
val token = peek()
if token.tokenType == TokenType.Error || token.tokenType == TokenType.End then
return Failure(new Exception(s"Encountered End or End token: ${token}"))
return Failure(new Exception(s"Encountered Error or End token: ${token}"))
expected match {
case Some(that) => {
@@ -42,7 +44,7 @@ class Parser(tokens: List[Token]):
case expected: TokenType if expected == token.tokenType => {}
case expected: List[String] if expected.contains(token.value) => {}
// case expected: List[TokenType] if expected.contains(token.tokenType) => {}
case _ => return Failure(new Exception(s"Token ${token} was not expected ${expected}"))
case _ => return Failure(new Exception(s"Token ${token} was not expected: ${that}"))
}
}
case None => return Failure(new Exception(s"Incorrect expected value: ${expected}"))
@@ -51,81 +53,66 @@ class Parser(tokens: List[Token]):
pos += 1
return Success(token)
def parseIntLiteral(): Try[Literal] =
def parseIntLiteral(): Literal =
val token = consume(Some(TokenType.IntLiteral))
return token match {
case Success(that) => {
that.value.toIntOption match {
case Some(int) => Success(Literal(int))
case None => Failure(new Exception(s"Token value not int: ${token}"))
}
case Success(that) => that.value.toIntOption match {
case Some(int) => Literal(int)
case None => throw new Exception(s"Token value not int: ${that.value}")
}
case Failure(that) => Failure(that)
case Failure(that) => throw that
}
def parseIdentifier(): Try[Identifier] =
def parseIdentifier(): Identifier =
val token = consume(Some(TokenType.Identifier))
return token match {
case Success(that) => Success(Identifier(that.value))
case Failure(that) => Failure(new Exception(s"Incorrect identifier token: ${token}"))
case Success(that) => Identifier(that.value)
case Failure(that) => throw that
}
def parseParenthisized(): Try[Expression] =
consume(Some("("))
def parseParenthisized(): Expression =
// Throw error if consume returns Failure
consume(Some("(")).get
val expression = parseExpression()
consume(Some(")"))
consume(Some(")")).get
return expression
def parseFactor(): Try[Expression] =
def parseFactor(): Expression =
val token = peek()
return token.tokenType match {
case TokenType.IntLiteral => parseIntLiteral()
case TokenType.Identifier => parseIdentifier()
case TokenType.Punctuation if token.value == "(" => parseParenthisized()
case _ => Failure(new Exception(s"Incorrect token type, expected (, int literal or identifier, got ${token.tokenType} token"))
case _ => throw new Exception(s"Incorrect token type: Expected (, int literal or identifier, got ${token.tokenType} token")
}
def parseTerm(): Try[Expression] =
def parseTerm(): Expression =
val operators = List("/", "*")
var left = parseFactor()
while
operators.contains(peek().value)
do
val operatorToken = consume(Some(operators))
val operatorToken = consume(Some(operators)).get
val right = parseFactor()
left = (left, operatorToken, right) match {
case (Success(l), Success(op), Success(r)) => Success(BinaryOperator(l, op.value, r))
case _ => Failure(new Exception(s"Failure in some value: l:${left}, op:${operatorToken}, r:${right}"))
}
left = BinaryOperator(left, operatorToken.value, right)
return left match {
case Success(that) => Success(that)
case Failure(that) => Failure(new Exception(s"$left"))
}
return left
def parseExpression(): Try[Expression] =
def parseExpression(): Expression =
val operators = List("+", "-")
var left = parseTerm()
while
operators.contains(peek().value)
do
val operatorToken = consume(Some(operators))
val operatorToken = consume(Some(operators)).get
val right = parseTerm()
left = (left, operatorToken, right) match {
case (Success(l), Success(op), Success(r)) => Success(BinaryOperator(l, op.value, r))
case _ => Failure(new Exception(s"Faiure in some value: l:${left}, op:${operatorToken}, r:${right}"))
}
left = BinaryOperator(left, operatorToken.value, right)
operators.contains(peek().value)
do ()
if peek().tokenType != TokenType.End then return Failure(new Exception("Tokens left when there should not be"))
return left match {
case Success(that)=> Success(that)
case Failure(that) => Failure(new Exception(s"Incorrect value ${left}"))
}
if peek().tokenType != TokenType.End then throw new Exception("Tokens left when there should not be")
return left
object Parser:
def parse(tokens: List[Token]): Try[Expression] =
+1 -1
View File
@@ -38,7 +38,7 @@ enum TokenType:
import compiler.Tokenizer.TokenType._
class Token(val value: String, val tokenType: TokenType ,val location: TokenLocation | TokenLocationDebug):
override def toString: String = s"${this.getClass.getName.stripPrefix("compiler.Tokenizer.")}($value, $location)"
override def toString: String = s"${this.getClass.getName.stripPrefix("compiler.Tokenizer.")}($value, $tokenType, $location)"
override def equals(other: Any): Boolean = other match {
case that: Token => {
that.location match {
+37 -80
View File
@@ -23,40 +23,31 @@ class ParserTests extends munit.FunSuite {
val result = p.parseIntLiteral()
val expected = Literal(7)
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse bigger ints correctly") {
val p = testParser("59819")
val result = p.parseIntLiteral()
val expected = Literal(59819)
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse separate ints correctly") {
val p = testParser("5 1 40")
val result1 = p.parseIntLiteral()
val result2 = p.parseIntLiteral()
val result3 = p.parseIntLiteral()
val r1 = p.parseIntLiteral()
val r2 = p.parseIntLiteral()
val r3 = p.parseIntLiteral()
val expected = List(Literal(5), Literal(1), Literal(40))
(result1, result2, result3) match {
case (Success(r1), Success(r2), Success(r3)) => assertEquals(List(r1, r2, r3), expected)
case _ => fail("Parsing failed")
}
assertEquals(List(r1, r2, r3), expected)
}
test("should not parse non-int values") {
val token = Tokenizer.tokenize("else").get(0)
val p = testParser("else")
val result = p.parseIntLiteral()
result match {
case Success(that) => fail("Parsing should have failed but succeeded: " + that)
case Failure(that) => assert(true)
interceptMessage[java.lang.Exception](s"Token $token was not expected: IntLiteral") {
val result = p.parseIntLiteral()
}
}
@@ -65,93 +56,72 @@ class ParserTests extends munit.FunSuite {
val p = testParser("1 + 2")
val result = p.parseExpression()
val expected = BinaryOperator(Literal(1), "+", Literal(2))
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse simple sum with identifiers") {
val p = testParser("1 + x")
val result = p.parseExpression()
val expected = BinaryOperator(Literal(1), "+", Identifier("x"))
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse simple sum with only identifiers") {
val p = testParser("value + tokenLen")
val result = p.parseExpression()
val expected = BinaryOperator(Identifier("value"), "+", Identifier("tokenLen"))
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should fail with missing operator") {
val errorToken = Tokenizer.tokenize("1 2").get(1)
val p = testParser("1 2")
val result = p.parseExpression()
result match {
case Success(that) => fail("Parsing should have failed but succeeded: " + that)
case Failure(that) => assert(true)
interceptMessage[java.lang.Exception](s"Token $errorToken was not expected: List(+, -)") {
val result = p.parseExpression()
}
}
test("should fail with missing ints") {
val p1 = testParser("1 +")
val result1 = p1.parseExpression()
val p2 = testParser("+ 2")
val result2 = p2.parseExpression()
(result1, result2) match {
case (Failure(r1), Failure(r2)) => assert(true)
case _ => fail("Parsing should have failed but succeeded" + result1 + result2)
interceptMessage[java.lang.Exception](s"Incorrect token type: Expected (, int literal or identifier, got End token") {
p1.parseExpression()
}
interceptMessage[java.lang.Exception](s"Incorrect token type: Expected (, int literal or identifier, got Operator token") {
p2.parseExpression()
}
}
test("should fail with incorrect operator") {
val token = Tokenizer.tokenize("1 while").get(1)
val p = testParser("1 while 2")
val result = p.parseExpression()
result match {
case Success(that) => fail("Parsing should have failed but succeeded: " + that)
case Failure(that) => assert(true)
interceptMessage[java.lang.Exception](s"Token $token was not expected: List(+, -)") {
p.parseExpression()
}
}
test("should fail with extra int") {
val p = testParser("1 + 2 3")
val result = p.parseExpression()
result match {
case Success(that) => fail("Parsing should have failed but succeeded: " + that)
case Failure(that) => assert(true)
interceptMessage[java.lang.Exception]("Tokens left when there should not be") {
p.parseExpression()
}
}
// IDENTIFIER
test("should parse simple identifier") {
val p = testParser("parse_int")
val result = p.parseIdentifier()
val expected = Identifier("parse_int")
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse multiple simple identifiers in a row") {
val p = testParser("parse_int BigInt")
val result1 = p.parseIdentifier()
val result2 = p.parseIdentifier()
val r1 = p.parseIdentifier()
val r2 = p.parseIdentifier()
val expected = List(Identifier("parse_int"), Identifier("BigInt"))
(result1, result2) match {
case (Success(r1), Success(r2)) => assertEquals(List(r1, r2), expected)
case (r1, r2) => fail(s"Parsing failed: ${r1}, ${r2}")
}
assertEquals(List(r1, r2), expected)
}
// TERM
@@ -160,43 +130,30 @@ class ParserTests extends munit.FunSuite {
val result = p.parseTerm()
val expected = Identifier("parse_int")
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse either identifier or int literal (int_literal)") {
val p = testParser("51080")
val result = p.parseTerm()
val expected = Literal(51080)
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
test("should parse either identifier or int literal (both)") {
val p = testParser("parse_int 14580")
val result1 = p.parseTerm()
val result2 = p.parseTerm()
val r1 = p.parseTerm()
val r2 = p.parseTerm()
val expected = List(Identifier("parse_int"), Literal(14580))
(result1, result2) match {
case (Success(r1), Success(r2)) => assertEquals(List(r1, r2), expected)
case (r1, r2) => fail(s"Parsing failed: ${r1}, ${r2}")
}
assertEquals(List(r1, r2), expected)
}
test("should parse multiple operators") {
val p = testParser("1 + 2 - 40")
val result = p.parseExpression()
val expected = BinaryOperator(BinaryOperator(Literal(1), "+", Literal(2)), "-", Literal(40))
result match {
case Success(that) => assertEquals(that, expected)
case Failure(that) => fail("Parsing failed: " + that)
}
assertEquals(result, expected)
}
}