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",
"Binary : Expr left, Token operator, Expr right",
"Call : Expr callee, Token paren, List<Expr> arguments",
"Get : Expr obj, Token name",
"Grouping : Expr expression",
"Literal : Any? value",
"Logical : Expr left, Token operator, Expr right",
"Set : Expr obj, Token name, Expr value",
"Unary : Token operator, Expr right",
"Variable : Token name"
)

@ -9,6 +9,10 @@ class AstPrinter : ExprVisitor<String> {
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 {
return parenthesize("=", expr)
}
@ -29,6 +33,10 @@ class AstPrinter : ExprVisitor<String> {
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 {
return parenthesize(expr.operator.lexeme, expr.right)
}

@ -7,9 +7,11 @@ interface ExprVisitor<R> {
fun visitAssign(expr: Assign): R
fun visitBinary(expr: Binary): R
fun visitCall(expr: Call): R
fun visitGet(expr: Get): R
fun visitGrouping(expr: Grouping): R
fun visitLiteral(expr: Literal): R
fun visitLogical(expr: Logical): R
fun visitSet(expr: Set): R
fun visitUnary(expr: Unary): 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(
val expression: 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(
val operator: Token, val right: Expr
) : Expr() {

@ -168,6 +168,14 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
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? {
return evaluate(expr.expression)
}
@ -188,6 +196,14 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
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? {
val right = evaluate(expr.right)

@ -1,5 +1,19 @@
package fr.celticinfo.lox
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"
}

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

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

@ -11,6 +11,10 @@ class RpnPrinter : ExprVisitor<String> {
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 {
return stack("=", expr)
}
@ -31,6 +35,10 @@ class RpnPrinter : ExprVisitor<String> {
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 {
return stack(expr.operator.lexeme, expr.right)
}

@ -837,4 +837,40 @@ var a = "global";
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]
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