diff --git a/compiler/src/main/scala/Parser.scala b/compiler/src/main/scala/Parser.scala index f83e558..7479d26 100644 --- a/compiler/src/main/scala/Parser.scala +++ b/compiler/src/main/scala/Parser.scala @@ -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] = diff --git a/compiler/src/main/scala/Tokenizer.scala b/compiler/src/main/scala/Tokenizer.scala index d61bf27..b43fa90 100644 --- a/compiler/src/main/scala/Tokenizer.scala +++ b/compiler/src/main/scala/Tokenizer.scala @@ -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 { diff --git a/compiler/src/test/scala/ParserTests.scala b/compiler/src/test/scala/ParserTests.scala index a6e8f0e..d6e68be 100644 --- a/compiler/src/test/scala/ParserTests.scala +++ b/compiler/src/test/scala/ParserTests.scala @@ -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) } }