From a2054be1165b6606669553385788b43b09d9a2ad Mon Sep 17 00:00:00 2001 From: Olivier Abrivard Date: Tue, 3 Sep 2024 10:09:01 +0200 Subject: [PATCH] Add For Loops --- src/main/fr/celticinfo/lox/AstPrinter.kt | 4 ++ src/main/fr/celticinfo/lox/Parser.kt | 37 +++++++++++++ src/main/fr/celticinfo/loxext/RpnPrinter.kt | 4 ++ src/test/fr/celticinfo/lox/InterpreterTest.kt | 53 +++++++++++++++++++ src/test/fr/celticinfo/lox/ParserTest.kt | 34 ++++++++++++ 5 files changed, 132 insertions(+) diff --git a/src/main/fr/celticinfo/lox/AstPrinter.kt b/src/main/fr/celticinfo/lox/AstPrinter.kt index 68cb927..203f173 100644 --- a/src/main/fr/celticinfo/lox/AstPrinter.kt +++ b/src/main/fr/celticinfo/lox/AstPrinter.kt @@ -21,6 +21,10 @@ class AstPrinter : ExprVisitor { return expr.value?.toString() ?: "nil" } + override fun visitLogical(expr: Logical): String { + return parenthesize(expr.operator.lexeme, expr.left, expr.right) + } + override fun visitUnary(expr: Unary): String { return parenthesize(expr.operator.lexeme, expr.right) } diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt index d67567a..fd5e1aa 100644 --- a/src/main/fr/celticinfo/lox/Parser.kt +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -54,6 +54,7 @@ class Parser(private val tokens: List) { private fun statement(): Stmt { return when { + match(FOR) -> forStatement() match(IF) -> ifStatement() match(PRINT) -> printStatement() match(WHILE) -> whileStatement() @@ -62,6 +63,42 @@ class Parser(private val tokens: List) { } } + private fun forStatement(): Stmt { + consume(LEFT_PAREN, "Expect '(' after 'for'.") + + val initializer = when { + match(SEMICOLON) -> null + match(VAR) -> varDeclaration() + else -> expressionStatement() + } + + val condition = when { + !check(SEMICOLON) -> expression() + else -> Literal(true) + } + consume(SEMICOLON, "Expect ';' after loop condition.") + + val increment = when { + !check(RIGHT_PAREN) -> expression() + else -> null + } + consume(RIGHT_PAREN, "Expect ')' after for clauses.") + + var body = statement() + + if (increment != null) { + body = Block(listOf(body, Expression(increment))) + } + + body = While(condition, body) + + if (initializer != null) { + body = Block(listOf(initializer, body)) + } + + return body + } + private fun ifStatement(): Stmt { consume(LEFT_PAREN, "Expect '(' after 'if'.") val condition = expression() diff --git a/src/main/fr/celticinfo/loxext/RpnPrinter.kt b/src/main/fr/celticinfo/loxext/RpnPrinter.kt index 6fb7f79..76040c6 100644 --- a/src/main/fr/celticinfo/loxext/RpnPrinter.kt +++ b/src/main/fr/celticinfo/loxext/RpnPrinter.kt @@ -23,6 +23,10 @@ class RpnPrinter : ExprVisitor { return expr.value?.toString() ?: "nil" } + override fun visitLogical(expr: Logical): String { + return stack(expr.operator.lexeme, expr.left, expr.right) + } + override fun visitUnary(expr: Unary): String { return stack(expr.operator.lexeme, expr.right) } diff --git a/src/test/fr/celticinfo/lox/InterpreterTest.kt b/src/test/fr/celticinfo/lox/InterpreterTest.kt index e8ac9f3..2924e83 100644 --- a/src/test/fr/celticinfo/lox/InterpreterTest.kt +++ b/src/test/fr/celticinfo/lox/InterpreterTest.kt @@ -305,4 +305,57 @@ else print "else"; System.setOut(standardOut) } } + + @Test + fun `Fibonacci test`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + val code = """ +var a = 0; +var temp; + +for (var b = 1; a < 10000; b = temp + b) { + print a; + temp = a; + a = b; +} + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(3, statements.size) + + Interpreter().interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("0\n" + + "1\n" + + "1\n" + + "2\n" + + "3\n" + + "5\n" + + "8\n" + + "13\n" + + "21\n" + + "34\n" + + "55\n" + + "89\n" + + "144\n" + + "233\n" + + "377\n" + + "610\n" + + "987\n" + + "1597\n" + + "2584\n" + + "4181\n" + + "6765", 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 45033f3..888e101 100644 --- a/src/test/fr/celticinfo/lox/ParserTest.kt +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -161,4 +161,38 @@ class ParserTest { 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) + } } \ No newline at end of file