diff --git a/.gitignore b/.gitignore index b63da45..199bc95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +### Kotlin ### +.kotlin/ + +### Gradle ### .gradle build/ !gradle/wrapper/gradle-wrapper.jar @@ -39,4 +43,4 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/src/main/ExprGenerator.ws.kts b/src/main/ExprGenerator.ws.kts index 5e90e2b..a2ea20b 100644 --- a/src/main/ExprGenerator.ws.kts +++ b/src/main/ExprGenerator.ws.kts @@ -52,6 +52,7 @@ defineAst("Expr", exprTypes) val stmtTypes = listOf( "Block : List statements", "Expression : Expr expression", + "If : Expr condition, Stmt thenBranch, Stmt? elseBranch", "Print : Expr expression", "Var : Token name, Expr? initializer" ) diff --git a/src/main/fr/celticinfo/lox/Interpreter.kt b/src/main/fr/celticinfo/lox/Interpreter.kt index 09dd715..8809f9a 100644 --- a/src/main/fr/celticinfo/lox/Interpreter.kt +++ b/src/main/fr/celticinfo/lox/Interpreter.kt @@ -44,6 +44,14 @@ class Interpreter: ExprVisitor, StmtVisitor{ evaluate(stmt.expression) } + override fun visitIf(stmt: If) { + if (isTruthy(evaluate(stmt.condition))) { + execute(stmt.thenBranch) + } else { + stmt.elseBranch?.let { execute(it) } + } + } + override fun visitPrint(stmt: Print) { val value = evaluate(stmt.expression) println(stringify(value)) diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt index a3b8548..2b7407a 100644 --- a/src/main/fr/celticinfo/lox/Parser.kt +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -46,12 +46,28 @@ class Parser(private val tokens: List) { private fun statement(): Stmt { return when { + match(IF) -> ifStatement() match(PRINT) -> printStatement() match(LEFT_BRACE) -> Block(blockStatement()) else -> expressionStatement() } } + private fun ifStatement(): Stmt { + consume(LEFT_PAREN, "Expect '(' after 'if'.") + val condition = expression() + consume(RIGHT_PAREN, "Expect ')' after if condition.") + + val thenBranch = statement() + + var elseBranch: Stmt? = null + if (match(ELSE)) { + elseBranch = statement() + } + + return If(condition, thenBranch, elseBranch) + } + private fun printStatement(): Stmt { val value = expression() consume(SEMICOLON, "Expect ';' after value.") diff --git a/src/main/fr/celticinfo/lox/Stmt.kt b/src/main/fr/celticinfo/lox/Stmt.kt index 51564fe..43c097c 100644 --- a/src/main/fr/celticinfo/lox/Stmt.kt +++ b/src/main/fr/celticinfo/lox/Stmt.kt @@ -6,6 +6,7 @@ package fr.celticinfo.lox interface StmtVisitor { fun visitBlock(stmt: Block): R fun visitExpression(stmt: Expression): R + fun visitIf(stmt: If): R fun visitPrint(stmt: Print): R fun visitVar(stmt: Var): R } @@ -33,6 +34,15 @@ data class Expression( } } +data class If( + val condition: Expr, + val thenBranch: Stmt, + val elseBranch: Stmt? +) : Stmt() { + override fun accept(visitor: StmtVisitor): R { + return visitor.visitIf(this) + } +} data class Print( val expression: Expr ) : Stmt() { diff --git a/src/test/fr/celticinfo/lox/InterpreterTest.kt b/src/test/fr/celticinfo/lox/InterpreterTest.kt index 0dff9da..e8ac9f3 100644 --- a/src/test/fr/celticinfo/lox/InterpreterTest.kt +++ b/src/test/fr/celticinfo/lox/InterpreterTest.kt @@ -201,4 +201,108 @@ print c; System.setOut(standardOut) } } + + @Test + fun `If statement should work`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + 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) + + Interpreter().interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("true", output) + } finally { + System.setOut(standardOut) + } + } + + @Test + fun `If statement with else should work`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + 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) + + Interpreter().interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("false", output) + } finally { + System.setOut(standardOut) + } + } + + @Test + fun `If statement with else if should work`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + val code = """ +if (1 > 2) print "true"; +else if (1 < 2) print "false"; + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(1, statements.size) + + Interpreter().interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("false", output) + } finally { + System.setOut(standardOut) + } + } + + @Test + fun `If statement with else if and else should work`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + val code = """ +if (1 > 2) print "true"; +else if (1 < 2) print "false"; +else print "else"; + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(1, statements.size) + + Interpreter().interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("false", output) + } finally { + System.setOut(standardOut) + } + } } \ No newline at end of file diff --git a/src/test/fr/celticinfo/lox/ParserTest.kt b/src/test/fr/celticinfo/lox/ParserTest.kt index 09674a0..45033f3 100644 --- a/src/test/fr/celticinfo/lox/ParserTest.kt +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -126,4 +126,39 @@ class ParserTest { { 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) + } } \ No newline at end of file