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