Add constructors and initializers

main
Olivier Abrivard 1 year ago
parent b84650ee61
commit 2fec77db1f

@ -50,7 +50,7 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
environment.define(stmt.name.lexeme, null)
val methods = stmt.methods.associate { method ->
val function = LoxFunction(method, environment)
val function = LoxFunction(method, environment, method.name.lexeme == "init")
method.name.lexeme to function
}

@ -1,5 +1,8 @@
package fr.celticinfo.lox
/**
* The LoxCallable interface represents a callable entity in the Lox language.
*/
interface LoxCallable {
fun call(interpreter: Interpreter, arguments: List<Any?>): Any?
fun arity(): Int

@ -1,8 +1,13 @@
package fr.celticinfo.lox
/**
* The LoxClass class represents a class in the Lox language.
*/
class LoxClass(private val name: String, private val methods: Map<String, LoxFunction>) : LoxCallable {
override fun call(interpreter: Interpreter, arguments: List<Any?>): Any? {
val instance = LoxInstance(this)
val initializer = findMethod("init")
initializer?.bind(instance)?.call(interpreter, arguments)
return instance
}
@ -10,7 +15,7 @@ class LoxClass(private val name: String, private val methods: Map<String, LoxFun
return methods[name]
}
override fun arity() = 0
override fun arity() = findMethod("init")?.arity() ?: 0
override fun toString() = name
}

@ -1,14 +1,18 @@
package fr.celticinfo.lox
/**
* The LoxFunction class represents a function in the Lox language.
*/
class LoxFunction(
private val declaration: Function,
private val closure: Environment
private val closure: Environment,
private val isInitializer: Boolean = false
) : LoxCallable {
fun bind(instance: LoxInstance): LoxFunction {
val environment = Environment(closure)
environment.define("this", instance)
return LoxFunction(declaration, environment)
return LoxFunction(declaration, environment, isInitializer)
}
override fun call(interpreter: Interpreter, arguments: List<Any?>): Any? {
@ -22,8 +26,16 @@ class LoxFunction(
interpreter.executeBlock(declaration.body, environment)
null
} catch (returnValue: LoxReturn) {
if (isInitializer) {
return closure.getAt(0, "this")
}
returnValue.value
}
if (isInitializer) {
return closure.getAt(0, "this")
}
}
override fun arity() = declaration.params.size

@ -30,7 +30,7 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
scopes.last()["this"] = true
for (method in stmt.methods) {
val declaration = FunctionType.METHOD
val declaration = if (method.name.lexeme == "init") FunctionType.INITIALIZER else FunctionType.METHOD
resolveFunction(method, declaration)
}
endScope()
@ -63,7 +63,12 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
Lox.error(stmt.keyword, "Cannot return from top-level code.")
}
stmt.value?.let { resolve(it) }
if (stmt.value != null) {
if (currentFunctionType == FunctionType.INITIALIZER) {
Lox.error(stmt.keyword, "Cannot return a value from an initializer.")
}
resolve(stmt.value)
}
}
override fun visitVar(stmt: Var) {
@ -197,6 +202,7 @@ class Resolver(private val interpreter: Interpreter) : ExprVisitor<Unit>, StmtVi
enum class FunctionType {
NONE,
FUNCTION,
INITIALIZER,
METHOD
}

@ -947,4 +947,46 @@ var a = "global";
System.setOut(standardOut)
}
}
@Test
fun `valid code with constructor`() {
val standardOut = System.out
val outputStreamCaptor = ByteArrayOutputStream()
System.setOut(PrintStream(outputStreamCaptor))
try {
val code = """
class Cake {
init(flavor) {
this.flavor = flavor;
}
taste() {
var adjective = "delicious";
print "The " + this.flavor + " cake is " + adjective + "!";
}
}
var cake = Cake("German chocolate");
cake.taste();
""".trimIndent()
val scanner = Scanner(code)
val tokens = scanner.scanTokens()
val parser = Parser(tokens)
val statements = parser.parse()
assertEquals(3, 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)
}
}
}

@ -336,4 +336,32 @@ class ParserTest {
assertTrue(statements[2] is Expression)
assertTrue(statements[3] is Expression)
}
@Test
fun `valid code with constructor`() {
val code = """
class Cake {
init(flavor) {
this.flavor = flavor;
}
taste() {
var adjective = "delicious";
print "The " + this.flavor + " cake is " + adjective + "!";
}
}
var cake = Cake("German chocolate");
cake.taste();
""".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 Var)
assertTrue(statements[2] is Expression)
}
}
Loading…
Cancel
Save