From fa461db4f247996cecb3e514e6658070a2cf4250 Mon Sep 17 00:00:00 2001 From: Olivier Abrivard Date: Mon, 30 Sep 2024 16:41:15 +0200 Subject: [PATCH] Add superclasses and subclasses --- src/main/ExprGenerator.ws.kts | 2 +- src/main/fr/celticinfo/lox/Interpreter.kt | 10 ++++++++- src/main/fr/celticinfo/lox/LoxClass.kt | 2 +- src/main/fr/celticinfo/lox/Parser.kt | 9 +++++++- src/main/fr/celticinfo/lox/Resolver.kt | 7 ++++++ src/main/fr/celticinfo/lox/Stmt.kt | 1 + src/test/fr/celticinfo/lox/ParserTest.kt | 27 +++++++++++++++++++++++ 7 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/main/ExprGenerator.ws.kts b/src/main/ExprGenerator.ws.kts index 770d734..3f38478 100644 --- a/src/main/ExprGenerator.ws.kts +++ b/src/main/ExprGenerator.ws.kts @@ -56,7 +56,7 @@ defineAst("Expr", exprTypes) val stmtTypes = listOf( "Block : List statements", - "ClassStmt : Token name, List methods", + "ClassStmt : Token name, Variable? superClass, List methods", "Expression : Expr expression", "Function : Token name, List params, List body", "If : Expr condition, Stmt thenBranch, Stmt? elseBranch", diff --git a/src/main/fr/celticinfo/lox/Interpreter.kt b/src/main/fr/celticinfo/lox/Interpreter.kt index 469dd42..ea26682 100644 --- a/src/main/fr/celticinfo/lox/Interpreter.kt +++ b/src/main/fr/celticinfo/lox/Interpreter.kt @@ -47,6 +47,14 @@ class Interpreter: ExprVisitor, StmtVisitor{ } override fun visitClassStmt(stmt: ClassStmt) { + val superClass = stmt.superClass?.let { + val evaluatedSuperClass = evaluate(it) + if (evaluatedSuperClass !is LoxClass) { + throw RuntimeError(it.name, "Superclass must be a class") + } + evaluatedSuperClass + } + environment.define(stmt.name.lexeme, null) val methods = stmt.methods.associate { method -> @@ -54,7 +62,7 @@ class Interpreter: ExprVisitor, StmtVisitor{ method.name.lexeme to function } - val klass = LoxClass(stmt.name.lexeme, methods) + val klass = LoxClass(stmt.name.lexeme, superClass, methods) environment.assign(stmt.name, klass) } diff --git a/src/main/fr/celticinfo/lox/LoxClass.kt b/src/main/fr/celticinfo/lox/LoxClass.kt index 6a8a63f..3ba047b 100644 --- a/src/main/fr/celticinfo/lox/LoxClass.kt +++ b/src/main/fr/celticinfo/lox/LoxClass.kt @@ -3,7 +3,7 @@ package fr.celticinfo.lox /** * The LoxClass class represents a class in the Lox language. */ -class LoxClass(private val name: String, private val methods: Map) : LoxCallable { +class LoxClass(private val name: String, private val superClass: LoxClass?, private val methods: Map) : LoxCallable { override fun call(interpreter: Interpreter, arguments: List): Any? { val instance = LoxInstance(this) val initializer = findMethod("init") diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt index c480407..e6ae396 100644 --- a/src/main/fr/celticinfo/lox/Parser.kt +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -35,6 +35,13 @@ class Parser(private val tokens: List) { private fun classDeclaration(): ClassStmt { val name = consume(IDENTIFIER, "Expect class name.") + + var superClass: Variable? = null + if (match(LESS)) { + consume(IDENTIFIER, "Expect superclass name.") + superClass = Variable(previous()) + } + consume(LEFT_BRACE, "Expect '{' before class body.") val methods: MutableList = ArrayList() @@ -44,7 +51,7 @@ class Parser(private val tokens: List) { consume(RIGHT_BRACE, "Expect '}' after class body.") - return ClassStmt(name, methods) + return ClassStmt(name, superClass, methods) } private fun function(kind: String): Function { diff --git a/src/main/fr/celticinfo/lox/Resolver.kt b/src/main/fr/celticinfo/lox/Resolver.kt index 204337a..5fb1178 100644 --- a/src/main/fr/celticinfo/lox/Resolver.kt +++ b/src/main/fr/celticinfo/lox/Resolver.kt @@ -26,6 +26,13 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor, StmtVi declare(stmt.name) define(stmt.name) + if (stmt.superClass != null) { + if (stmt.name.lexeme == stmt.superClass.name.lexeme) { + Lox.error(stmt.superClass.name, "A class cannot inherit from itself.") + } + resolve(stmt.superClass) + } + beginScope() scopes.last()["this"] = true diff --git a/src/main/fr/celticinfo/lox/Stmt.kt b/src/main/fr/celticinfo/lox/Stmt.kt index 0efc957..7e5830c 100644 --- a/src/main/fr/celticinfo/lox/Stmt.kt +++ b/src/main/fr/celticinfo/lox/Stmt.kt @@ -32,6 +32,7 @@ data class Block( data class ClassStmt( val name: Token, + val superClass: Variable?, val methods: List ) : Stmt() { override fun accept(visitor: StmtVisitor): R { diff --git a/src/test/fr/celticinfo/lox/ParserTest.kt b/src/test/fr/celticinfo/lox/ParserTest.kt index dc57997..08c67ac 100644 --- a/src/test/fr/celticinfo/lox/ParserTest.kt +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -364,4 +364,31 @@ class ParserTest { assertTrue(statements[2] is Expression) } + @Test + fun `valid code with inheritance`() { + val code = """ + class Doughnut { + cook() { + print "Fry until golden brown."; + } + } + + class BostonCream < Doughnut { + cook() { + super.cook(); + print "Pipe full of custard and coat with chocolate."; + } + } + + BostonCream().cook(); + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val statements = parser.parse() + assertEquals(3,statements.size) + assertTrue(statements[0] is ClassStmt) + assertTrue(statements[1] is ClassStmt) + assertTrue(statements[2] is Expression) + } } \ No newline at end of file