diff --git a/src/main/ExprGenerator.ws.kts b/src/main/ExprGenerator.ws.kts index d296f67..770d734 100644 --- a/src/main/ExprGenerator.ws.kts +++ b/src/main/ExprGenerator.ws.kts @@ -48,6 +48,7 @@ val exprTypes = listOf( "Literal : Any? value", "Logical : Expr left, Token operator, Expr right", "Set : Expr obj, Token name, Expr value", + "This : Token keyword", "Unary : Token operator, Expr right", "Variable : Token name" ) diff --git a/src/main/fr/celticinfo/lox/AstPrinter.kt b/src/main/fr/celticinfo/lox/AstPrinter.kt index e510bdc..bd66cdc 100644 --- a/src/main/fr/celticinfo/lox/AstPrinter.kt +++ b/src/main/fr/celticinfo/lox/AstPrinter.kt @@ -37,6 +37,10 @@ class AstPrinter : ExprVisitor { return parenthesize("set", expr.obj, Literal(expr.name), expr.value) } + override fun visitThis(expr: This): String { + return "this" + } + override fun visitUnary(expr: Unary): String { return parenthesize(expr.operator.lexeme, expr.right) } diff --git a/src/main/fr/celticinfo/lox/Expr.kt b/src/main/fr/celticinfo/lox/Expr.kt index f70389e..90993ba 100644 --- a/src/main/fr/celticinfo/lox/Expr.kt +++ b/src/main/fr/celticinfo/lox/Expr.kt @@ -12,6 +12,7 @@ interface ExprVisitor { fun visitLiteral(expr: Literal): R fun visitLogical(expr: Logical): R fun visitSet(expr: Set): R + fun visitThis(expr: This): R fun visitUnary(expr: Unary): R fun visitVariable(expr: Variable): R } @@ -95,6 +96,14 @@ data class Set( } } +data class This( + val keyword: Token +) : Expr() { + override fun accept(visitor: ExprVisitor): R { + return visitor.visitThis(this) + } +} + data class Unary( val operator: Token, val right: Expr ) : Expr() { diff --git a/src/main/fr/celticinfo/lox/Interpreter.kt b/src/main/fr/celticinfo/lox/Interpreter.kt index 3930de3..b763174 100644 --- a/src/main/fr/celticinfo/lox/Interpreter.kt +++ b/src/main/fr/celticinfo/lox/Interpreter.kt @@ -211,6 +211,10 @@ class Interpreter: ExprVisitor, StmtVisitor{ return value } + override fun visitThis(expr: This): Any? { + return lookUpVariable(expr.keyword, expr) + } + override fun visitUnary(expr: Unary): Any? { val right = evaluate(expr.right) diff --git a/src/main/fr/celticinfo/lox/LoxFunction.kt b/src/main/fr/celticinfo/lox/LoxFunction.kt index d7b0e2e..fa0a5ae 100644 --- a/src/main/fr/celticinfo/lox/LoxFunction.kt +++ b/src/main/fr/celticinfo/lox/LoxFunction.kt @@ -5,6 +5,12 @@ class LoxFunction( private val closure: Environment ) : LoxCallable { + fun bind(instance: LoxInstance): LoxFunction { + val environment = Environment(closure) + environment.define("this", instance) + return LoxFunction(declaration, environment) + } + override fun call(interpreter: Interpreter, arguments: List): Any? { val environment = Environment(closure) diff --git a/src/main/fr/celticinfo/lox/LoxInstance.kt b/src/main/fr/celticinfo/lox/LoxInstance.kt index 120c548..b152ac4 100644 --- a/src/main/fr/celticinfo/lox/LoxInstance.kt +++ b/src/main/fr/celticinfo/lox/LoxInstance.kt @@ -10,7 +10,7 @@ class LoxInstance(private val klass: LoxClass) { val method = klass.findMethod(name.lexeme) if (method != null) { - return method + return method.bind(this) } throw RuntimeError(name, "Undefined property '${name.lexeme}'.") diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt index 73f7c02..c480407 100644 --- a/src/main/fr/celticinfo/lox/Parser.kt +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -336,6 +336,7 @@ class Parser(private val tokens: List) { match(TRUE) -> return Literal(true) match(NIL) -> return Literal(null) match(NUMBER, STRING) -> return Literal(previous().literal) + match(THIS) -> return This(previous()) match(IDENTIFIER) -> return Variable(previous()) match(LEFT_PAREN) -> { val expr = expression() diff --git a/src/main/fr/celticinfo/lox/Resolver.kt b/src/main/fr/celticinfo/lox/Resolver.kt index d2d69e7..d11d017 100644 --- a/src/main/fr/celticinfo/lox/Resolver.kt +++ b/src/main/fr/celticinfo/lox/Resolver.kt @@ -4,27 +4,38 @@ 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>() + private val scopes = mutableListOf>() private var currentFunctionType = FunctionType.NONE + private var currentClassType = ClassType.NONE - init { - scopes.add(mutableMapOf()) - } - override fun visitBlock(stmt: Block) { - beginScope() - resolve(stmt.statements) - endScope() - } + init { + scopes.add(mutableMapOf()) + } + + override fun visitBlock(stmt: Block) { + beginScope() + resolve(stmt.statements) + endScope() + } override fun visitClassStmt(stmt: ClassStmt) { + val enclosingClassType = currentClassType + currentClassType = ClassType.CLASS + declare(stmt.name) define(stmt.name) + beginScope() + scopes.last()["this"] = true + for (method in stmt.methods) { val declaration = FunctionType.METHOD resolveFunction(method, declaration) } + endScope() + + currentClassType = enclosingClassType } override fun visitExpression(stmt: Expression) { @@ -110,6 +121,14 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor, StmtVi resolve(expr.obj) } + override fun visitThis(expr: This) { + if (currentClassType == ClassType.NONE) { + Lox.error(expr.keyword, "Cannot use 'this' outside of a class.") + return + } + resolveLocal(expr, expr.keyword) + } + override fun visitUnary(expr: Unary) { resolve(expr.right) } @@ -180,3 +199,8 @@ enum class FunctionType { FUNCTION, METHOD } + +enum class ClassType { + NONE, + CLASS +} \ No newline at end of file diff --git a/src/main/fr/celticinfo/loxext/RpnPrinter.kt b/src/main/fr/celticinfo/loxext/RpnPrinter.kt index 5a341ee..4324af9 100644 --- a/src/main/fr/celticinfo/loxext/RpnPrinter.kt +++ b/src/main/fr/celticinfo/loxext/RpnPrinter.kt @@ -39,6 +39,10 @@ class RpnPrinter : ExprVisitor { return stack("set", expr.obj, Literal(expr.name), expr.value) } + override fun visitThis(expr: This): String { + return "this" + } + override fun visitUnary(expr: Unary): String { return stack(expr.operator.lexeme, expr.right) } diff --git a/src/test/fr/celticinfo/lox/InterpreterTest.kt b/src/test/fr/celticinfo/lox/InterpreterTest.kt index eedd132..eee9aad 100644 --- a/src/test/fr/celticinfo/lox/InterpreterTest.kt +++ b/src/test/fr/celticinfo/lox/InterpreterTest.kt @@ -907,4 +907,44 @@ var a = "global"; } finally { System.setOut(standardOut) } - }} + } + + @Test + fun `valid code with this`() { + val standardOut = System.out + val outputStreamCaptor = ByteArrayOutputStream() + + System.setOut(PrintStream(outputStreamCaptor)) + + try { + val code = """ + class Cake { + taste() { + var adjective = "delicious"; + print "The " + this.flavor + " cake is " + adjective + "!"; + } + } + + var cake = Cake(); + cake.flavor = "German chocolate"; + cake.taste(); + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(4, statements.size) + + val interpreter = Interpreter() + + val resolver = Resolver(interpreter) + resolver.resolve(statements) + + interpreter.interpret(statements) + val output = outputStreamCaptor.toString().trim() + assertEquals("The German chocolate cake is delicious!", output) + } finally { + System.setOut(standardOut) + } + } +} diff --git a/src/test/fr/celticinfo/lox/ParserTest.kt b/src/test/fr/celticinfo/lox/ParserTest.kt index 14bbe56..3aa9a6e 100644 --- a/src/test/fr/celticinfo/lox/ParserTest.kt +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -311,4 +311,29 @@ class ParserTest { assertTrue(statements[0] is ClassStmt) assertTrue(statements[1] is Expression) } + + @Test + fun `valid code with this`() { + val code = """ + class Cake { + taste() { + var adjective = "delicious"; + print "The " + this.flavor + " cake is " + adjective + "!"; + } + } + + var cake = Cake(); + cake.flavor = "German chocolate"; + cake.taste(); + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(4,statements.size) + assertTrue(statements[0] is ClassStmt) + assertTrue(statements[1] is Var) + assertTrue(statements[2] is Expression) + assertTrue(statements[3] is Expression) + } } \ No newline at end of file