diff --git a/src/main/fr/celticinfo/lox/Interpreter.kt b/src/main/fr/celticinfo/lox/Interpreter.kt index 6091077..77207f6 100644 --- a/src/main/fr/celticinfo/lox/Interpreter.kt +++ b/src/main/fr/celticinfo/lox/Interpreter.kt @@ -3,6 +3,22 @@ package fr.celticinfo.lox import fr.celticinfo.lox.TokenType.* class Interpreter: ExprVisitor{ + + fun interpret(expr: Expr): Any? { + return try { + val value = evaluate(expr) + println(stringify(value)) + value + } catch (error: RuntimeError) { + Lox.runtimeError(error) + null + } + } + + private fun evaluate(expr: Expr): Any? { + return expr.accept(this) + } + override fun visitBinary(binary: Binary): Any? { val left = evaluate(binary.left) val right = evaluate(binary.right) @@ -83,10 +99,6 @@ class Interpreter: ExprVisitor{ } } - private fun evaluate(expr: Expr): Any? { - return expr.accept(this) - } - private fun isEqual(left: Any?, right: Any?): Boolean { return if (left == null && right == null) { true @@ -108,4 +120,18 @@ class Interpreter: ExprVisitor{ throw RuntimeError(operator, "Operands must be numbers") } } + + private fun stringify(obj: Any?): String { + return when (obj) { + null -> "nil" + is Double -> { + var text = obj.toString() + if (text.endsWith(".0")) { + text = text.substring(0, text.length - 2) + } + text + } + else -> obj.toString() + } + } } \ No newline at end of file diff --git a/src/main/fr/celticinfo/lox/Lox.kt b/src/main/fr/celticinfo/lox/Lox.kt index 63d3719..b102902 100644 --- a/src/main/fr/celticinfo/lox/Lox.kt +++ b/src/main/fr/celticinfo/lox/Lox.kt @@ -3,11 +3,9 @@ package fr.celticinfo.lox import kotlin.system.exitProcess fun main(args: Array) { - val lox = Lox() - when (args.size) { - 0 -> lox.runPrompt() - 1 -> lox.runFile(args.first()) + 0 -> Lox.runPrompt() + 1 -> Lox.runFile(args.first()) else -> {println("Usage: jlox [script]") ; exitProcess(64)} } } @@ -15,8 +13,10 @@ fun main(args: Array) { /** * Lox is the main class that runs the Lox interpreter. */ -class Lox { +object Lox { private var hadError = false + private var hadRuntimeError = false + private val interpreter = Interpreter() fun runPrompt() { while (true) { @@ -33,6 +33,7 @@ class Lox { run(String(bytes)) // Indicate an error in the exit code. if (hadError) exitProcess(65) + if (hadRuntimeError) exitProcess(70) } private fun run(source: String) { @@ -44,24 +45,28 @@ class Lox { // Stop if there was a syntax error. if (hadError) return - println(AstPrinter().print(expression!!)) + interpreter.interpret(expression!!) } - companion object { - fun error(line: Int, s: String) { - report(line, "", s) - } + fun error(line: Int, s: String) { + report(line, "", s) + } - fun error(token: Token, message: String) { - if (token.type == TokenType.EOF) { - report(token.line, " at end", message) - } else { - report(token.line, " at '${token.lexeme}'", message) - } + fun error(token: Token, message: String) { + if (token.type == TokenType.EOF) { + report(token.line, " at end", message) + } else { + report(token.line, " at '${token.lexeme}'", message) } + } - private fun report(line: Int, where: String, message: String) { - System.err.println("[line $line] Error$where: $message") - } + fun runtimeError(error: RuntimeError) { + System.err.println("${error.message}\n[line ${error.token.line}]") + hadRuntimeError = true + } + + private fun report(line: Int, where: String, message: String) { + System.err.println("[line $line] Error$where: $message") + hadError = true } } \ No newline at end of file diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt index c8abc4c..6f4b09d 100644 --- a/src/main/fr/celticinfo/lox/Parser.kt +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -11,7 +11,13 @@ class Parser(private val tokens: List) { fun parse(): Expr? { return try { - expression() + val result = expression() + + if (!isAtEnd()) { + throw error(peek(), "Expect end of expression.") + } + + result } catch (error: ParseError) { null } diff --git a/src/test/fr/celticinfo/lox/InterpreterTest.kt b/src/test/fr/celticinfo/lox/InterpreterTest.kt new file mode 100644 index 0000000..811d8e8 --- /dev/null +++ b/src/test/fr/celticinfo/lox/InterpreterTest.kt @@ -0,0 +1,46 @@ +package fr.celticinfo.lox + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* + +class InterpreterTest { + + @Test + fun `validate interpreter`() { + val code = """ + (1 + 2 * 3 - 5) / 2 + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val expr = parser.parse() + val value = Interpreter().interpret(expr!!) + assertEquals(1.0, value) + } + + @Test + fun `Division by zero should raise error`() { + val code = """ + 1 / 0 + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val expr = parser.parse() + val value = Interpreter().interpret(expr!!) + assertNull(value) + } + + @Test + fun `Invalid type raise error`() { + val code = """ + 1 + false + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val expr = parser.parse() + val value = Interpreter().interpret(expr!!) + assertNull(value) + } +} \ 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 e663801..d5c70f3 100644 --- a/src/test/fr/celticinfo/lox/ParserTest.kt +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -29,4 +29,16 @@ class ParserTest { val expr = parser.parse() assertNull(expr) } + + @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 expr = parser.parse() + assertNull(expr) + } } \ No newline at end of file