Add properties on instances

main
Olivier Abrivard 1 year ago
parent 3e9fca6593
commit 22a4d86160

@ -43,9 +43,11 @@ val exprTypes = listOf(
"Assign : Token name, Expr value", "Assign : Token name, Expr value",
"Binary : Expr left, Token operator, Expr right", "Binary : Expr left, Token operator, Expr right",
"Call : Expr callee, Token paren, List<Expr> arguments", "Call : Expr callee, Token paren, List<Expr> arguments",
"Get : Expr obj, Token name",
"Grouping : Expr expression", "Grouping : Expr expression",
"Literal : Any? value", "Literal : Any? value",
"Logical : Expr left, Token operator, Expr right", "Logical : Expr left, Token operator, Expr right",
"Set : Expr obj, Token name, Expr value",
"Unary : Token operator, Expr right", "Unary : Token operator, Expr right",
"Variable : Token name" "Variable : Token name"
) )

@ -9,6 +9,10 @@ class AstPrinter : ExprVisitor<String> {
return parenthesize("call", expr.callee, *expr.arguments.toTypedArray()) return parenthesize("call", expr.callee, *expr.arguments.toTypedArray())
} }
override fun visitGet(expr: Get): String {
return parenthesize("get", expr.obj, Literal(expr.name))
}
override fun visitAssign(expr: Assign): String { override fun visitAssign(expr: Assign): String {
return parenthesize("=", expr) return parenthesize("=", expr)
} }
@ -29,6 +33,10 @@ class AstPrinter : ExprVisitor<String> {
return parenthesize(expr.operator.lexeme, expr.left, expr.right) return parenthesize(expr.operator.lexeme, expr.left, expr.right)
} }
override fun visitSet(expr: Set): String {
return parenthesize("set", expr.obj, Literal(expr.name), expr.value)
}
override fun visitUnary(expr: Unary): String { override fun visitUnary(expr: Unary): String {
return parenthesize(expr.operator.lexeme, expr.right) return parenthesize(expr.operator.lexeme, expr.right)
} }

@ -7,9 +7,11 @@ interface ExprVisitor<R> {
fun visitAssign(expr: Assign): R fun visitAssign(expr: Assign): R
fun visitBinary(expr: Binary): R fun visitBinary(expr: Binary): R
fun visitCall(expr: Call): R fun visitCall(expr: Call): R
fun visitGet(expr: Get): R
fun visitGrouping(expr: Grouping): R fun visitGrouping(expr: Grouping): R
fun visitLiteral(expr: Literal): R fun visitLiteral(expr: Literal): R
fun visitLogical(expr: Logical): R fun visitLogical(expr: Logical): R
fun visitSet(expr: Set): R
fun visitUnary(expr: Unary): R fun visitUnary(expr: Unary): R
fun visitVariable(expr: Variable): R fun visitVariable(expr: Variable): R
} }
@ -48,6 +50,15 @@ data class Call(
} }
} }
data class Get(
val obj: Expr,
val name: Token
) : Expr() {
override fun <R> accept(visitor: ExprVisitor<R>): R {
return visitor.visitGet(this)
}
}
data class Grouping( data class Grouping(
val expression: Expr val expression: Expr
) : Expr() { ) : Expr() {
@ -74,6 +85,16 @@ data class Logical(
} }
} }
data class Set(
val obj: Expr,
val name: Token,
val value: Expr
) : Expr() {
override fun <R> accept(visitor: ExprVisitor<R>): R {
return visitor.visitSet(this)
}
}
data class Unary( data class Unary(
val operator: Token, val right: Expr val operator: Token, val right: Expr
) : Expr() { ) : Expr() {

@ -168,6 +168,14 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
return callee.call(this, arguments) return callee.call(this, arguments)
} }
override fun visitGet(expr: Get): Any? {
val obj = evaluate(expr.obj)
if (obj is LoxInstance) {
return obj.get(expr.name)
}
throw RuntimeError(expr.name, "Only instances have properties")
}
override fun visitGrouping(expr: Grouping): Any? { override fun visitGrouping(expr: Grouping): Any? {
return evaluate(expr.expression) return evaluate(expr.expression)
} }
@ -188,6 +196,14 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
return evaluate(expr.right) return evaluate(expr.right)
} }
override fun visitSet(expr: Set): Any? {
val obj = evaluate(expr.obj) as? LoxInstance
?: throw RuntimeError(expr.name, "Only instances have fields")
val value = evaluate(expr.value)
obj.set(expr.name, value)
return value
}
override fun visitUnary(expr: Unary): Any? { override fun visitUnary(expr: Unary): Any? {
val right = evaluate(expr.right) val right = evaluate(expr.right)

@ -1,5 +1,19 @@
package fr.celticinfo.lox package fr.celticinfo.lox
class LoxInstance(private val klass: LoxClass) { class LoxInstance(private val klass: LoxClass) {
private val fields = mutableMapOf<String, Any?>()
fun get(name: Token): Any? {
if (fields.containsKey(name.lexeme)) {
return fields[name.lexeme]
}
throw RuntimeError(name, "Undefined property '${name.lexeme}'.")
}
fun set(name: Token, value: Any?) {
fields[name.lexeme] = value
}
override fun toString() = "$klass instance" override fun toString() = "$klass instance"
} }

@ -194,6 +194,8 @@ class Parser(private val tokens: List<Token>) {
if (expr is Variable) { if (expr is Variable) {
val name = expr.name val name = expr.name
return Assign(name, value) return Assign(name, value)
} else if (expr is Get) {
return Set(expr.obj, expr.name, value)
} }
error(equals, "Invalid assignment target.") error(equals, "Invalid assignment target.")
@ -300,7 +302,11 @@ class Parser(private val tokens: List<Token>) {
while (true) { while (true) {
if (match(LEFT_PAREN)) { if (match(LEFT_PAREN)) {
expr = finishCall(expr) expr = finishCall(expr)
} else { } else if (match(DOT)) {
val name = consume(IDENTIFIER, "Expect property name after '.'.")
expr = Get(expr, name)
}
else {
break break
} }
} }

@ -83,6 +83,10 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
expr.arguments.forEach { resolve(it) } expr.arguments.forEach { resolve(it) }
} }
override fun visitGet(expr: Get) {
resolve(expr.obj)
}
override fun visitGrouping(expr: Grouping) { override fun visitGrouping(expr: Grouping) {
resolve(expr.expression) resolve(expr.expression)
} }
@ -96,6 +100,11 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
resolve(expr.right) resolve(expr.right)
} }
override fun visitSet(expr: Set) {
resolve(expr.value)
resolve(expr.obj)
}
override fun visitUnary(expr: Unary) { override fun visitUnary(expr: Unary) {
resolve(expr.right) resolve(expr.right)
} }

@ -11,6 +11,10 @@ class RpnPrinter : ExprVisitor<String> {
return stack("call", expr.callee, *expr.arguments.toTypedArray()) return stack("call", expr.callee, *expr.arguments.toTypedArray())
} }
override fun visitGet(expr: Get): String {
return stack("get", expr.obj, Literal(expr.name))
}
override fun visitAssign(expr: Assign): String { override fun visitAssign(expr: Assign): String {
return stack("=", expr) return stack("=", expr)
} }
@ -31,6 +35,10 @@ class RpnPrinter : ExprVisitor<String> {
return stack(expr.operator.lexeme, expr.left, expr.right) return stack(expr.operator.lexeme, expr.left, expr.right)
} }
override fun visitSet(expr: fr.celticinfo.lox.Set): String {
return stack("set", expr.obj, Literal(expr.name), expr.value)
}
override fun visitUnary(expr: Unary): String { override fun visitUnary(expr: Unary): String {
return stack(expr.operator.lexeme, expr.right) return stack(expr.operator.lexeme, expr.right)
} }

@ -837,4 +837,40 @@ var a = "global";
System.setOut(standardOut) System.setOut(standardOut)
} }
} }
@Test
fun `valid code with properties initialization and access`() {
val standardOut = System.out
val outputStreamCaptor = ByteArrayOutputStream()
System.setOut(PrintStream(outputStreamCaptor))
try {
val code = """
class DevonshireCream {
}
var cream = DevonshireCream();
cream.color = "White";
print cream.color;
""".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("White", output)
} finally {
System.setOut(standardOut)
}
}
} }

@ -270,4 +270,25 @@ class ParserTest {
val stmt = statements[1] val stmt = statements[1]
assertTrue(stmt is Var) assertTrue(stmt is Var)
} }
@Test
fun `valid code with properties`() {
val code = """
class DevonshireCream {
}
var cream = DevonshireCream();
cream.color = "White";
print cream.color;
""".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 Print)
}
} }
Loading…
Cancel
Save