Inheritance 13

This commit is contained in:
nazrin 2025-06-02 23:44:23 +00:00
parent d8ac625429
commit 848c846e09
12 changed files with 117 additions and 7 deletions

1
.gitignore vendored
View file

@ -16,4 +16,5 @@ lox-test-*
*.lst *.lst
.msc/ .msc/
test/test.lox

View file

@ -18,6 +18,7 @@ abstract class Expr{
R visit(Literal expr); R visit(Literal expr);
R visit(Logical expr); R visit(Logical expr);
R visit(Set expr); R visit(Set expr);
R visit(Super expr);
R visit(This expr); R visit(This expr);
R visit(Unary expr); R visit(Unary expr);
R visit(Variable expr); R visit(Variable expr);
@ -73,6 +74,11 @@ abstract class Expr{
Expr value; Expr value;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }
static class Super : typeof(this){
Token keyword;
Token method;
mixin defCtorAndAccept;
}
static class This : typeof(this){ static class This : typeof(this){
Token keyword; Token keyword;
mixin defCtorAndAccept; mixin defCtorAndAccept;

View file

@ -1,6 +1,7 @@
module jlox.interpreter; module jlox.interpreter;
import std.conv; import std.conv;
import std.exception : enforce;
import std.stdio; import std.stdio;
import std.algorithm; import std.algorithm;
import std.array; import std.array;
@ -109,13 +110,24 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
executeBlock(stmt.statements, new Environment(environment)); executeBlock(stmt.statements, new Environment(environment));
} }
void visit(Stmt.Class stmt){ void visit(Stmt.Class stmt){
LoxValue superclass;
if(stmt.superclass !is null){
superclass = evaluate(stmt.superclass);
enforce(cast(LoxClass)superclass, new RuntimeError(stmt.superclass.name, "Superclass must be a class."));
}
environment.define(stmt.name.lexeme, new LoxNil()); environment.define(stmt.name.lexeme, new LoxNil());
if(stmt.superclass !is null){
environment = new Environment(environment);
environment.define("super", superclass);
}
LoxFunction[string] methods; LoxFunction[string] methods;
foreach(Stmt.Function method; stmt.methods){ foreach(Stmt.Function method; stmt.methods){
LoxFunction func = new LoxFunction(method, environment, method.name.lexeme == "init"); LoxFunction func = new LoxFunction(method, environment, method.name.lexeme == "init");
methods[method.name.lexeme] = func; methods[method.name.lexeme] = func;
} }
LoxClass cls = new LoxClass(stmt.name.lexeme, methods); LoxClass cls = new LoxClass(stmt.name.lexeme, cast(LoxClass)superclass, methods);
if(superclass !is null)
environment = environment.enclosing;
environment.assign(stmt.name, cls); environment.assign(stmt.name, cls);
} }
void visit(Stmt.Expression stmt){ void visit(Stmt.Expression stmt){
@ -188,6 +200,15 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
(cast(LoxInstance)object).set(expr.name, value); (cast(LoxInstance)object).set(expr.name, value);
return value; return value;
} }
LoxValue visit(Expr.Super expr){
uint distance = locals[expr];
LoxClass superclass = cast(LoxClass)environment.getAt(distance, "super");
LoxInstance object = cast(LoxInstance)environment.getAt(distance - 1, "this");
LoxFunction method = superclass.findMethod(expr.method.lexeme);
if(method is null)
throw new RuntimeError(expr.method, "Undefined property '" ~ expr.method.lexeme ~ "'.");
return method.bind(object);
}
LoxValue visit(Expr.This expr){ LoxValue visit(Expr.This expr){
return lookUpVariable(expr.keyword, expr); return lookUpVariable(expr.keyword, expr);
} }

View file

@ -8,12 +8,15 @@ import common.util;
class LoxClass : LoxCallable{ class LoxClass : LoxCallable{
package const string name; package const string name;
private const LoxClass superclass;
private const LoxFunction[string] methods; private const LoxFunction[string] methods;
mixin defaultCtor; mixin defaultCtor;
LoxFunction findMethod(string name){ LoxFunction findMethod(string name) const{
if(auto method = name in methods) if(auto method = name in methods)
return cast(LoxFunction)*method; return cast(LoxFunction)*method;
if(superclass !is null)
return superclass.findMethod(name);
return null; return null;
} }

View file

@ -312,6 +312,12 @@ class Parser{
consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
return new Expr.Grouping(expr); return new Expr.Grouping(expr);
} }
if(match(TokenType.SUPER)){
Token keyword = previous();
consume(TokenType.DOT, "Expect '.' after 'super'.");
Token method = consume(TokenType.IDENTIFIER, "Expect superclass method name.");
return new Expr.Super(keyword, method);
}
throw error(peek(), "Expect expression."); throw error(peek(), "Expect expression.");
} }
private Stmt varDeclaration(){ private Stmt varDeclaration(){
@ -324,12 +330,17 @@ class Parser{
} }
private Stmt classDeclaration() { private Stmt classDeclaration() {
Token name = consume(TokenType.IDENTIFIER, "Expect class name."); Token name = consume(TokenType.IDENTIFIER, "Expect class name.");
Expr.Variable superclass;
if(match(TokenType.LESS)){
consume(TokenType.IDENTIFIER, "Expect superlcass name.");
superclass = new Expr.Variable(previous);
}
consume(TokenType.LEFT_BRACE, "Expect '{' before class body."); consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
Stmt.Function[] methods; Stmt.Function[] methods;
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd) while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
methods ~= fun("method"); methods ~= fun("method");
consume(TokenType.RIGHT_BRACE, "Expect '}' after class body."); consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
return new Stmt.Class(name, methods); return new Stmt.Class(name, superclass, methods);
} }
private Stmt declaration(){ private Stmt declaration(){
try { try {

View file

@ -14,7 +14,7 @@ import common.util;
class Resolver : Stmt.Visitor!void, Expr.Visitor!void { class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
private enum FunctionType{ NONE, FUNCTION, INITIALISER, METHOD } private enum FunctionType{ NONE, FUNCTION, INITIALISER, METHOD }
private enum ClassType{ NONE, CLASS } private enum ClassType{ NONE, CLASS, SUBCLASS }
private Interpreter interpreter; private Interpreter interpreter;
this(Interpreter interpreter){ this(Interpreter interpreter){
this.interpreter = interpreter; this.interpreter = interpreter;
@ -80,6 +80,16 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
currentClass = enclosingClass; currentClass = enclosingClass;
declare(stmt.name); declare(stmt.name);
define(stmt.name); define(stmt.name);
if(stmt.superclass !is null){
currentClass = ClassType.SUBCLASS;
if(stmt.name.lexeme == stmt.superclass.name.lexeme)
Lox.error(stmt.superclass.name, "A class can't inherit from itself.");
resolve(stmt.superclass);
}
if(stmt.superclass !is null){
beginScope();
scopes.front["super"] = true;
}
beginScope(); beginScope();
scopes.front["this"] = true; scopes.front["this"] = true;
foreach(method; stmt.methods){ foreach(method; stmt.methods){
@ -89,6 +99,8 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
resolveFunction(method, declaration); resolveFunction(method, declaration);
} }
endScope(); endScope();
if(stmt.superclass !is null)
endScope();
} }
void visit(Stmt.Var stmt){ void visit(Stmt.Var stmt){
declare(stmt.name); declare(stmt.name);
@ -165,6 +177,13 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
resolve(expr.value); resolve(expr.value);
resolve(expr.object); resolve(expr.object);
} }
void visit(Expr.Super expr){
if(currentClass == ClassType.NONE)
Lox.error(expr.keyword, "Can't use 'super' outside of a class.");
else if (currentClass != ClassType.SUBCLASS)
Lox.error(expr.keyword, "Can't use 'super' in a class with no superclass.");
resolveLocal(expr, expr.keyword);
}
void visit(Expr.This expr){ void visit(Expr.This expr){
if(currentClass == ClassType.NONE){ if(currentClass == ClassType.NONE){
Lox.error(expr.keyword, "Can't use 'this' outside of a class."); Lox.error(expr.keyword, "Can't use 'this' outside of a class.");

View file

@ -36,6 +36,7 @@ abstract class Stmt{
} }
static class Class : typeof(this){ static class Class : typeof(this){
Token name; Token name;
Expr.Variable superclass;
Function[] methods; Function[] methods;
mixin defCtorAndAccept; mixin defCtorAndAccept;
} }

View file

@ -16,12 +16,15 @@ void main(){
"./test/fib_recursive.lox".match(fib(34)); "./test/fib_recursive.lox".match(fib(34));
"./test/fib_closure.lox".match(fib(34)); "./test/fib_closure.lox".match(fib(34));
"./test/class.lox".match("The German chocolate cake is delicious!\n"); "./test/class.lox".match("The German chocolate cake is delicious!\n");
"./test/super.lox".match("Fry until golden brown.\nPipe full of custard and coat with chocolate.\nA method\n");
"./test/err/invalid_syntax.lox".shouldFail(RetVal.other);
"./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name"); "./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name");
"./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable"); "./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable");
"./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable"); "./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable");
"./test/err/invalid_syntax.lox".shouldFail(RetVal.other);
"./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code"); "./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code");
"./test/err/super_outside_class.lox".shouldFail(RetVal.other, "Can't use 'super' outside of a class");
"./test/err/super_without_superclass.lox".shouldFail(RetVal.other, "Can't use 'super' in a class with no superclass");
} }
enum RetVal{ enum RetVal{

View file

@ -0,0 +1,3 @@
super.notEvenInAClass();

View file

@ -0,0 +1,8 @@
class Eclair{
cook(){
super.cook();
print "Pipe full of crème pâtissière.";
}
}

36
test/super.lox Normal file
View file

@ -0,0 +1,36 @@
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();
class A{
method(){
print "A method";
}
}
class B < A{
method(){
print "B method";
}
test(){
super.method();
}
}
class C < B{}
C().test();

View file

@ -1,2 +0,0 @@