Add support for [this]

main
Olivier Abrivard 1 year ago
parent fd71a46ede
commit b84650ee61

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

@ -37,6 +37,10 @@ class AstPrinter : ExprVisitor<String> {
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)
}

@ -12,6 +12,7 @@ interface ExprVisitor<R> {
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 <R> accept(visitor: ExprVisitor<R>): R {
return visitor.visitThis(this)
}
}
data class Unary(
val operator: Token, val right: Expr
) : Expr() {

@ -211,6 +211,10 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
return value
}
override fun visitThis(expr: This): Any? {
return lookUpVariable(expr.keyword, expr)
}
override fun visitUnary(expr: Unary): Any? {
val right = evaluate(expr.right)

@ -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?>): Any? {
val environment = Environment(closure)

@ -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}'.")

@ -336,6 +336,7 @@ class Parser(private val tokens: List<Token>) {
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()

@ -6,6 +6,8 @@ package fr.celticinfo.lox
class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVisitor<Unit> {
private val scopes = mutableListOf<MutableMap<String, Boolean>>()
private var currentFunctionType = FunctionType.NONE
private var currentClassType = ClassType.NONE
init {
scopes.add(mutableMapOf())
@ -18,13 +20,22 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
}
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<Unit>, 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
}

@ -39,6 +39,10 @@ class RpnPrinter : ExprVisitor<String> {
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)
}

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

@ -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)
}
}
Loading…
Cancel
Save