Completed Expression evaluation

main
oabrivard 2 years ago
parent 35974645a4
commit 78c65bbf39

@ -3,6 +3,22 @@ package fr.celticinfo.lox
import fr.celticinfo.lox.TokenType.* import fr.celticinfo.lox.TokenType.*
class Interpreter: ExprVisitor<Any?>{ class Interpreter: ExprVisitor<Any?>{
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? { override fun visitBinary(binary: Binary): Any? {
val left = evaluate(binary.left) val left = evaluate(binary.left)
val right = evaluate(binary.right) val right = evaluate(binary.right)
@ -83,10 +99,6 @@ class Interpreter: ExprVisitor<Any?>{
} }
} }
private fun evaluate(expr: Expr): Any? {
return expr.accept(this)
}
private fun isEqual(left: Any?, right: Any?): Boolean { private fun isEqual(left: Any?, right: Any?): Boolean {
return if (left == null && right == null) { return if (left == null && right == null) {
true true
@ -108,4 +120,18 @@ class Interpreter: ExprVisitor<Any?>{
throw RuntimeError(operator, "Operands must be numbers") 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()
}
}
} }

@ -3,11 +3,9 @@ package fr.celticinfo.lox
import kotlin.system.exitProcess import kotlin.system.exitProcess
fun main(args: Array<String>) { fun main(args: Array<String>) {
val lox = Lox()
when (args.size) { when (args.size) {
0 -> lox.runPrompt() 0 -> Lox.runPrompt()
1 -> lox.runFile(args.first()) 1 -> Lox.runFile(args.first())
else -> {println("Usage: jlox [script]") ; exitProcess(64)} else -> {println("Usage: jlox [script]") ; exitProcess(64)}
} }
} }
@ -15,8 +13,10 @@ fun main(args: Array<String>) {
/** /**
* Lox is the main class that runs the Lox interpreter. * Lox is the main class that runs the Lox interpreter.
*/ */
class Lox { object Lox {
private var hadError = false private var hadError = false
private var hadRuntimeError = false
private val interpreter = Interpreter()
fun runPrompt() { fun runPrompt() {
while (true) { while (true) {
@ -33,6 +33,7 @@ class Lox {
run(String(bytes)) run(String(bytes))
// Indicate an error in the exit code. // Indicate an error in the exit code.
if (hadError) exitProcess(65) if (hadError) exitProcess(65)
if (hadRuntimeError) exitProcess(70)
} }
private fun run(source: String) { private fun run(source: String) {
@ -44,24 +45,28 @@ class Lox {
// Stop if there was a syntax error. // Stop if there was a syntax error.
if (hadError) return if (hadError) return
println(AstPrinter().print(expression!!)) interpreter.interpret(expression!!)
} }
companion object { fun error(line: Int, s: String) {
fun error(line: Int, s: String) { report(line, "", s)
report(line, "", s) }
}
fun error(token: Token, message: String) { fun error(token: Token, message: String) {
if (token.type == TokenType.EOF) { if (token.type == TokenType.EOF) {
report(token.line, " at end", message) report(token.line, " at end", message)
} else { } else {
report(token.line, " at '${token.lexeme}'", message) report(token.line, " at '${token.lexeme}'", message)
}
} }
}
private fun report(line: Int, where: String, message: String) { fun runtimeError(error: RuntimeError) {
System.err.println("[line $line] Error$where: $message") 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
} }
} }

@ -11,7 +11,13 @@ class Parser(private val tokens: List<Token>) {
fun parse(): Expr? { fun parse(): Expr? {
return try { return try {
expression() val result = expression()
if (!isAtEnd()) {
throw error(peek(), "Expect end of expression.")
}
result
} catch (error: ParseError) { } catch (error: ParseError) {
null null
} }

@ -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)
}
}

@ -29,4 +29,16 @@ class ParserTest {
val expr = parser.parse() val expr = parser.parse()
assertNull(expr) 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)
}
} }
Loading…
Cancel
Save