You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
7.1 KiB
Kotlin

package fr.celticinfo.lox
import fr.celticinfo.lox.TokenType.*
class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
var globals = Environment()
private var environment = globals
constructor() {
globals.define("clock", object : LoxCallable {
override fun arity(): Int {
return 0
}
override fun call(interpreter: Interpreter, arguments: List<Any?>): Any? {
return System.currentTimeMillis().toDouble() / 1000.0
}
override fun toString(): String {
return "<native fn>"
}
})
}
fun interpret(statements: List<Stmt?>) {
try {
for (statement in statements) {
execute(statement)
}
} catch (error: RuntimeError) {
Lox.runtimeError(error)
throw error
}
}
private fun execute(stmt: Stmt?) {
stmt?.accept(this)
}
override fun visitBlock(stmt: Block) {
executeBlock(stmt.statements, Environment(environment))
}
fun executeBlock(statements: List<Stmt?>, environment: Environment) {
val previous = this.environment
try {
this.environment = environment
for (statement in statements) {
execute(statement)
}
} finally {
this.environment = previous
}
}
private fun evaluate(expr: Expr?): Any? {
return expr?.accept(this)
}
override fun visitExpression(stmt: Expression) {
evaluate(stmt.expression)
}
override fun visitFunction(stmt: Function) {
val function = LoxFunction(stmt, environment)
environment.define(stmt.name.lexeme, function)
}
override fun visitIf(stmt: If) {
if (isTruthy(evaluate(stmt.condition))) {
execute(stmt.thenBranch)
} else {
stmt.elseBranch?.let { execute(it) }
}
}
override fun visitWhile(stmt: While) {
while (isTruthy(evaluate(stmt.condition))) {
execute(stmt.body)
}
}
override fun visitPrint(stmt: Print) {
val value = evaluate(stmt.expression)
println(stringify(value))
}
override fun visitReturn(stmt: Return) {
val value = stmt.value?.let { evaluate(it) }
throw LoxReturn(value)
}
override fun visitVar(stmt: Var) {
val value = evaluate(stmt.initializer)
environment.define(stmt.name.lexeme, value)
}
override fun visitAssign(expr: Assign): Any? {
val value = evaluate(expr.value)
environment.assign(expr.name, value)
return value
}
override fun visitBinary(expr: Binary): Any? {
val left = evaluate(expr.left)
val right = evaluate(expr.right)
return when (expr.operator.type) {
MINUS -> {
checkNumberOperands(expr.operator, left, right)
left as Double - right as Double
}
SLASH -> {
checkNumberOperands(expr.operator, left, right)
if (right == 0.0) {
throw RuntimeError(expr.operator, "Division by zero")
}
left as Double / right as Double
}
STAR -> {
checkNumberOperands(expr.operator, left, right)
left as Double * right as Double
}
PLUS -> {
if (left is Double && right is Double) {
left + right
} else if (left is String && right is String) {
left + right
} else {
throw RuntimeError(expr.operator, "Operands must be two numbers or two strings")
}
}
GREATER -> {
checkNumberOperands(expr.operator, left, right)
left as Double > right as Double
}
GREATER_EQUAL -> {
checkNumberOperands(expr.operator, left, right)
left as Double >= right as Double
}
LESS -> {
checkNumberOperands(expr.operator, left, right)
(left as Double) < right as Double
}
LESS_EQUAL -> {
checkNumberOperands(expr.operator, left, right)
left as Double <= right as Double
}
BANG_EQUAL -> return !isEqual(left, right)
EQUAL_EQUAL -> return isEqual(left, right)
else -> null
}
}
override fun visitCall(expr: Call): Any? {
val callee = evaluate(expr.callee)
val arguments = expr.arguments.map { evaluate(it) }
if (callee !is LoxCallable) {
throw RuntimeError(expr.paren, "Can only call functions and classes")
}
if (arguments.size != callee.arity()) {
throw RuntimeError(expr.paren, "Expected ${callee.arity()} arguments but got ${arguments.size}")
}
return callee.call(this, arguments)
}
override fun visitGrouping(expr: Grouping): Any? {
return evaluate(expr.expression)
}
override fun visitLiteral(expr: Literal): Any? {
return expr.value
}
override fun visitLogical(expr: Logical): Any? {
val left = evaluate(expr.left)
if (expr.operator.type == OR) {
if (isTruthy(left)) return left
} else {
if (!isTruthy(left)) return left
}
return evaluate(expr.right)
}
override fun visitUnary(expr: Unary): Any? {
val right = evaluate(expr.right)
return when (expr.operator.type) {
MINUS -> {
checkNumberOperand(expr.operator, right)
-(right as Double)
}
BANG -> !isTruthy(right)
else -> null
}
}
override fun visitVariable(expr: Variable): Any? {
return environment.get(expr.name)
}
private fun isTruthy(obj: Any?): Boolean {
return when (obj) {
null -> false
is Boolean -> obj
else -> true
}
}
private fun isEqual(left: Any?, right: Any?): Boolean {
return if (left == null && right == null) {
true
} else if (left == null) {
false
} else {
left == right
}
}
private fun checkNumberOperand(operator: Token, right: Any?) {
if (right !is Double) {
throw RuntimeError(operator, "Operand must be a number")
}
}
private fun checkNumberOperands(operator: Token, left: Any?, right: Any?) {
if (left !is Double || right !is Double) {
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()
}
}
}