package fr.celticinfo.lox import fr.celticinfo.loxext.RpnPrinter import org.junit.jupiter.api.assertAll import kotlin.test.* import kotlin.test.Test class ParserTest { @Test fun `validate parser`() { val code = """ 1 + 2 * 3 - 4 / 5; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is Expression) val expr = stmt.expression assertEquals("1.0 2.0 3.0 * + 4.0 5.0 / -", RpnPrinter().print(expr)) } @Test fun `invalid code generates parsing error`() { val code = """ 1 + (2 """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertNull(stmt) } @Test fun `invalid expression generates parsing error`() { val code = """ 1(2 """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertNull(stmt) } @Test fun `valid code with multiple statements`() { val code = """ var a = 1; var b = 2; print a + b; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(3,statements.size) assertAll( { assertTrue(statements[0] is Var) }, { assertTrue(statements[1] is Var) }, { assertTrue(statements[2] is Print) } ) } @Test fun `valid code with block`() { val code = """ { var a = 1; var b = 2; print a + b; } """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is Block) val block = stmt.statements assertEquals(3,block.size) assertAll( { assertTrue(block[0] is Var) }, { assertTrue(block[1] is Var) }, { assertTrue(block[2] is Print) } ) } @Test fun `valid code with block and nested block`() { val code = """ { var a = 1; { var b = 2; print a + b; } } """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is Block) val block = stmt.statements assertEquals(2,block.size) assertAll( { assertTrue(block[0] is Var) }, { assertTrue(block[1] is Block) } ) val nestedBlock = block[1] as Block val nestedStatements = nestedBlock.statements assertEquals(2,nestedStatements.size) assertAll( { assertTrue(nestedStatements[0] is Var) }, { assertTrue(nestedStatements[1] is Print) } ) } @Test fun `valid code with if statement`() { val code = """ if (1 < 2) print "true"; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is If) val ifStmt = stmt as If assertTrue(ifStmt.condition is Binary) assertTrue(ifStmt.thenBranch is Print) } @Test fun `valid code with if else statement`() { val code = """ if (1 < 2) print "true"; else print "false"; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is If) val ifStmt = stmt as If assertTrue(ifStmt.condition is Binary) assertTrue(ifStmt.thenBranch is Print) assertTrue(ifStmt.elseBranch is Print) } @Test fun `valid code with while statement`() { val code = """ while (1 < 2) print "true"; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is While) val whileStmt = stmt as While assertTrue(whileStmt.condition is Binary) assertTrue(whileStmt.body is Print) } @Test fun `valid code with for statement`() { val code = """ for (var i = 0; i < 10; i = i + 1) print i; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is Block) val block = stmt as Block val blockStatements = block.statements assertEquals(2,blockStatements.size) } @Test fun `valid code with function declaration`() { val code = """ fun add(a, b) { return a + b; } """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(1,statements.size) val stmt = statements.first() assertTrue(stmt is Function) val function = stmt as Function assertEquals("add",function.name.lexeme) assertEquals(2,function.params.size) assertEquals(1,function.body.size) } @Test fun `valid code with function call`() { val code = """ fun add(a, b) { return a + b; } print add(1, 2); """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(2,statements.size) val stmt = statements[1] assertTrue(stmt is Print) val printStmt = stmt as Print assertTrue(printStmt.expression is Call) } @Test fun `valid code with class declaration`() { val code = """ class DevonshireCream { serveOn() { return "Scones"; } } print DevonshireCream; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(2,statements.size) val stmt = statements[0] assertTrue(stmt is ClassStmt) } @Test fun `valid code with class instantiation`() { val code = """ class DevonshireCream {} var cream = DevonshireCream(); print cream; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(3,statements.size) val stmt = statements[1] assertTrue(stmt is Var) } @Test fun `valid code with properties`() { val code = """ class DevonshireCream { } var cream = DevonshireCream(); cream.color = "White"; print cream.color; """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(4,statements.size) assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is Var) assertTrue(statements[2] is Expression) assertTrue(statements[3] is Print) } @Test fun `valid code with methods`() { val code = """ class Bacon { eat() { print "Crunch crunch crunch!"; } } Bacon().eat(); """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(2,statements.size) assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is Expression) } @Test fun `valid code with this`() { val code = """ class Cake { taste() { var adjective = "delicious"; print "The " + this.flavor + " cake is " + adjective + "!"; } } var cake = Cake(); cake.flavor = "German chocolate"; cake.taste(); """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(4,statements.size) assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is Var) assertTrue(statements[2] is Expression) assertTrue(statements[3] is Expression) } @Test fun `valid code with constructor`() { val code = """ class Cake { init(flavor) { this.flavor = flavor; } taste() { var adjective = "delicious"; print "The " + this.flavor + " cake is " + adjective + "!"; } } var cake = Cake("German chocolate"); cake.taste(); """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(3,statements.size) assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is Var) assertTrue(statements[2] is Expression) } @Test fun `valid code with inheritance`() { val code = """ class Doughnut { cook() { print "Fry until golden brown."; } } class BostonCream < Doughnut { cook() { super.cook(); print "Pipe full of custard and coat with chocolate."; } } BostonCream().cook(); """.trimIndent() val scanner = Scanner(code) val tokens = scanner.scanTokens() val parser = Parser(tokens) val statements = parser.parse() assertEquals(3,statements.size) assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is ClassStmt) assertTrue(statements[2] is Expression) } }