diff --git a/src/main/ExprGenerator.ws.kts b/src/main/ExprGenerator.ws.kts index 314a92a..677965a 100644 --- a/src/main/ExprGenerator.ws.kts +++ b/src/main/ExprGenerator.ws.kts @@ -1,3 +1,5 @@ +import java.util.* + val types = listOf( "Binary : Expr left, Token operator, Expr right", "Grouping : Expr expression", @@ -6,10 +8,23 @@ val types = listOf( ) println("package fr.celticinfo.lox") + println() -println("sealed class Expr {") +println("interface ExprVisitor {") +for (type in types) { + val parts = type.split(":") + val name = parts[0].trim() + println(" fun visit$name(${name.lowercase(Locale.getDefault())}: $name): R") +} println("}") +println() +println("""/** + * The Expr class represents the different types of expressions that can be parsed by the Parser. + */""".trimIndent()) +println("sealed class Expr {") +println(" abstract fun accept(visitor: ExprVisitor): R") +println("}") for (type in types) { val parts = type.split(":") val name = parts[0].trim() @@ -23,5 +38,9 @@ for (type in types) { val sep = if (field == fields.last()) "" else "," println(" val $name: $type$sep") } - println(") : Expr()") -} \ No newline at end of file + println(") : Expr() {") + println(" override fun accept(visitor: ExprVisitor): R {") + println(" return visitor.visit$name(this)") + println(" }") + println("}") +} diff --git a/src/main/fr/celticinfo/lox/AstPrinter.kt b/src/main/fr/celticinfo/lox/AstPrinter.kt new file mode 100644 index 0000000..5692f34 --- /dev/null +++ b/src/main/fr/celticinfo/lox/AstPrinter.kt @@ -0,0 +1,34 @@ +package fr.celticinfo.lox + +class AstPrinter : ExprVisitor { + fun print(expr: Expr): String { + return expr.accept(this) + } + + override fun visitBinary(binary: Binary): String { + return parenthesize(binary.operator.lexeme, binary.left, binary.right) + } + + override fun visitGrouping(grouping: Grouping): String { + return parenthesize("group", grouping.expression) + } + + override fun visitLiteral(literal: Literal): String { + return literal.value?.toString() ?: "nil" + } + + override fun visitUnary(unary: Unary): String { + return parenthesize(unary.operator.lexeme, unary.right) + } + + private fun parenthesize(name: String, vararg exprs: Expr): String { + val builder = StringBuilder() + builder.append("(").append(name) + for (expr in exprs) { + builder.append(" ") + builder.append(expr.accept(this)) + } + builder.append(")") + return builder.toString() + } +} \ No newline at end of file diff --git a/src/main/fr/celticinfo/lox/Expr.kt b/src/main/fr/celticinfo/lox/Expr.kt index 4a2b22d..a3c623f 100644 --- a/src/main/fr/celticinfo/lox/Expr.kt +++ b/src/main/fr/celticinfo/lox/Expr.kt @@ -1,26 +1,48 @@ package fr.celticinfo.lox +interface ExprVisitor { + fun visitBinary(binary: Binary): R + fun visitGrouping(grouping: Grouping): R + fun visitLiteral(literal: Literal): R + fun visitUnary(unary: Unary): R + +} + /** * The Expr class represents the different types of expressions that can be parsed by the Parser. */ sealed class Expr { + abstract fun accept(visitor: ExprVisitor): R } data class Binary( - val left: Expr, - val operator: Token, - val right: Expr -) : Expr() + val left: Expr, val operator: Token, val right: Expr +) : Expr() { + override fun accept(visitor: ExprVisitor): R { + return visitor.visitBinary(this) + } +} data class Grouping( val expression: Expr -) : Expr() +) : Expr() { + override fun accept(visitor: ExprVisitor): R { + return visitor.visitGrouping(this) + } +} data class Literal( val value: Any -) : Expr() +) : Expr() { + override fun accept(visitor: ExprVisitor): R { + return visitor.visitLiteral(this) + } +} data class Unary( - val operator: Token, - val right: Expr -) : Expr() \ No newline at end of file + val operator: Token, val right: Expr +) : Expr() { + override fun accept(visitor: ExprVisitor): R { + return visitor.visitUnary(this) + } +} \ No newline at end of file diff --git a/src/main/fr/celticinfo/loxext/RpnPrinter.kt b/src/main/fr/celticinfo/loxext/RpnPrinter.kt new file mode 100644 index 0000000..525e49f --- /dev/null +++ b/src/main/fr/celticinfo/loxext/RpnPrinter.kt @@ -0,0 +1,35 @@ +package fr.celticinfo.loxext + +import fr.celticinfo.lox.* + +class RpnPrinter : ExprVisitor { + fun print(expr: Expr): String { + return expr.accept(this) + } + + override fun visitBinary(binary: Binary): String { + return stack(binary.operator.lexeme, binary.left, binary.right) + } + + override fun visitGrouping(grouping: Grouping): String { + return stack("", grouping.expression) + } + + override fun visitLiteral(literal: Literal): String { + return literal.value?.toString() ?: "nil" + } + + override fun visitUnary(unary: Unary): String { + return stack(unary.operator.lexeme, unary.right) + } + + private fun stack(name: String, vararg exprs: Expr): String { + val builder = StringBuilder() + for (expr in exprs) { + builder.append(expr.accept(this)) + if (name.isNotEmpty()) builder.append(" ") + } + builder.append(name) + return builder.toString() + } +} diff --git a/src/test/fr/celticinfo/lox/AstPrinterTest.kt b/src/test/fr/celticinfo/lox/AstPrinterTest.kt new file mode 100644 index 0000000..34439bc --- /dev/null +++ b/src/test/fr/celticinfo/lox/AstPrinterTest.kt @@ -0,0 +1,50 @@ +package fr.celticinfo.lox + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class AstPrinterTest { + private val astPrinter = AstPrinter() + + @Test + fun print() { + val expression: Expr = Binary( + Unary( + Token(TokenType.MINUS, "-", null, 1), Literal(123) + ), Token(TokenType.STAR, "*", null, 1), Grouping( + Literal(45.67) + ) + ) + assertEquals("(* (- 123) (group 45.67))", astPrinter.print(expression)) + } + + @Test + fun visitBinary() { + val expression: Expr = Binary( + Literal(1), Token(TokenType.PLUS, "+", null, 1), Literal(2) + ) + assertEquals("(+ 1 2)", astPrinter.print(expression)) + } + + @Test + fun visitGrouping() { + val expression: Expr = Grouping( + Literal(123) + ) + assertEquals("(group 123)", astPrinter.print(expression)) + } + + @Test + fun visitLiteral() { + val expression: Expr = Literal(123) + assertEquals("123", astPrinter.print(expression)) + } + + @Test + fun visitUnary() { + val expression: Expr = Unary( + Token(TokenType.MINUS, "-", null, 1), Literal(123) + ) + assertEquals("(- 123)", astPrinter.print(expression)) + } +} \ No newline at end of file diff --git a/src/test/fr/celticinfo/loxext/RpnPrinterTest.kt b/src/test/fr/celticinfo/loxext/RpnPrinterTest.kt new file mode 100644 index 0000000..c0c3cde --- /dev/null +++ b/src/test/fr/celticinfo/loxext/RpnPrinterTest.kt @@ -0,0 +1,70 @@ +package fr.celticinfo.loxext + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +import fr.celticinfo.lox.* + +class RpnPrinterTest { + private val rpnPrinter = RpnPrinter() + + @Test + fun print() { + val expression: Expr = Binary( + Unary( + Token(TokenType.MINUS, "-", null, 1), Literal(123) + ), Token(TokenType.STAR, "*", null, 1), Grouping( + Literal(45.67) + ) + ) + assertEquals("123 - 45.67 *", rpnPrinter.print(expression)) + } + + @Test + fun testComplexExpr() { + val expression: Expr = Binary( + Grouping( + Binary( + Literal(1), Token(TokenType.PLUS, "+", null, 1), Literal(2) + ) + ), + Token(TokenType.STAR, "*", null, 1), + Grouping( + Binary( + Literal(4), Token(TokenType.MINUS, "-", null, 1), Literal(3) + ) + ) + ) + assertEquals("1 2 + 4 3 - *", rpnPrinter.print(expression)) + } + + @Test + fun visitBinary() { + val expression: Expr = Binary( + Literal(1), Token(TokenType.PLUS, "+", null, 1), Literal(2) + ) + assertEquals("1 2 +", rpnPrinter.print(expression)) + } + + @Test + fun visitGrouping() { + val expression: Expr = Grouping( + Literal(123) + ) + assertEquals("123", rpnPrinter.print(expression)) + } + + @Test + fun visitLiteral() { + val expression: Expr = Literal(123) + assertEquals("123", rpnPrinter.print(expression)) + } + + @Test + fun visitUnary() { + val expression: Expr = Unary( + Token(TokenType.MINUS, "-", null, 1), Literal(123) + ) + assertEquals("123 -", rpnPrinter.print(expression)) + } +} \ No newline at end of file