diff --git a/src/main/fr/celticinfo/lox/Resolver.kt b/src/main/fr/celticinfo/lox/Resolver.kt new file mode 100644 index 0000000..98218e8 --- /dev/null +++ b/src/main/fr/celticinfo/lox/Resolver.kt @@ -0,0 +1,147 @@ +package fr.celticinfo.lox + +/** + * The Resolver class is used to resolve the scope of the variables. + */ +class Resolver(private val interpreter: Interpreter) : ExprVisitor, StmtVisitor { + private val scopes = mutableListOf>() + + init { + scopes.add(mutableMapOf()) + } + + override fun visitBlock(stmt: Block) { + beginScope() + resolve(stmt.statements) + endScope() + } + + override fun visitExpression(stmt: Expression) { + resolve(stmt.expression) + } + + override fun visitFunction(stmt: Function) { + declare(stmt.name) + define(stmt.name) + resolveFunction(stmt) + } + + override fun visitIf(stmt: If) { + resolve(stmt.condition) + resolve(stmt.thenBranch) + stmt.elseBranch?.let { resolve(it) } + } + + override fun visitPrint(stmt: Print) { + resolve(stmt.expression) + } + + override fun visitReturn(stmt: Return) { + stmt.value?.let { resolve(it) } + } + + override fun visitVar(stmt: Var) { + declare(stmt.name) + resolve(stmt.initializer) + define(stmt.name) + } + + override fun visitWhile(stmt: While) { + resolve(stmt.condition) + resolve(stmt.body) + } + + override fun visitVariable(expr: Variable) { + if (!scopes.isEmpty() && scopes.last()[expr.name.lexeme] == false) { + Lox.error(expr.name, "Cannot read local variable in its own initializer.") + } + resolveLocal(expr, expr.name) + } + + override fun visitAssign(expr: Assign) { + resolve(expr.value) + resolveLocal(expr, expr.name) + } + + override fun visitBinary(expr: Binary) { + resolve(expr.left) + resolve(expr.right) + } + + override fun visitCall(expr: Call) { + resolve(expr.callee) + expr.arguments.forEach { resolve(it) } + } + + override fun visitGrouping(expr: Grouping) { + resolve(expr.expression) + } + + override fun visitLiteral(expr: Literal) { + // Do nothing + } + + override fun visitLogical(expr: Logical) { + resolve(expr.left) + resolve(expr.right) + } + + override fun visitUnary(expr: Unary) { + resolve(expr.right) + } + + private fun resolve(statements: List) { + for (statement in statements) { + resolve(statement) + } + } + + private fun resolve(stmt: Stmt?) { + stmt?.accept(this) + } + + private fun resolve(expr: Expr?) { + expr?.accept(this) + } + + private fun beginScope() { + scopes.add(mutableMapOf()) + } + + private fun endScope() { + scopes.removeLast() + } + + private fun declare(name: Token) { + if (scopes.isEmpty()) return + val scope = scopes.last() + if (scope.containsKey(name.lexeme)) { + Lox.error(name, "Variable with this name already declared in this scope.") + } + scope[name.lexeme] = false + } + + private fun define(name: Token) { + if (scopes.isEmpty()) return + scopes.last()[name.lexeme] = true + } + + private fun resolveLocal(expr: Expr, name: Token) { + for (i in scopes.size - 1 downTo 0) { + if (scopes[i].containsKey(name.lexeme)) { + interpreter.resolve(expr, scopes.size - 1 - i) + return + } + } + } + + private fun resolveFunction(stmt: Function) { + beginScope() + for (param in stmt.params) { + declare(param) + define(param) + } + resolve(stmt.body) + endScope() + } +} \ No newline at end of file