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
.msc/
test/test.lox

View file

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

View file

@ -1,6 +1,7 @@
module jlox.interpreter;
import std.conv;
import std.exception : enforce;
import std.stdio;
import std.algorithm;
import std.array;
@ -109,13 +110,24 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
executeBlock(stmt.statements, new Environment(environment));
}
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());
if(stmt.superclass !is null){
environment = new Environment(environment);
environment.define("super", superclass);
}
LoxFunction[string] methods;
foreach(Stmt.Function method; stmt.methods){
LoxFunction func = new LoxFunction(method, environment, method.name.lexeme == "init");
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);
}
void visit(Stmt.Expression stmt){
@ -188,6 +200,15 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!LoxValue {
(cast(LoxInstance)object).set(expr.name, 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){
return lookUpVariable(expr.keyword, expr);
}

View file

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

View file

@ -312,6 +312,12 @@ class Parser{
consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
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.");
}
private Stmt varDeclaration(){
@ -324,12 +330,17 @@ class Parser{
}
private Stmt classDeclaration() {
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.");
Stmt.Function[] methods;
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
methods ~= fun("method");
consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
return new Stmt.Class(name, methods);
return new Stmt.Class(name, superclass, methods);
}
private Stmt declaration(){
try {

View file

@ -14,7 +14,7 @@ import common.util;
class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
private enum FunctionType{ NONE, FUNCTION, INITIALISER, METHOD }
private enum ClassType{ NONE, CLASS }
private enum ClassType{ NONE, CLASS, SUBCLASS }
private Interpreter interpreter;
this(Interpreter interpreter){
this.interpreter = interpreter;
@ -80,6 +80,16 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
currentClass = enclosingClass;
declare(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();
scopes.front["this"] = true;
foreach(method; stmt.methods){
@ -89,6 +99,8 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
resolveFunction(method, declaration);
}
endScope();
if(stmt.superclass !is null)
endScope();
}
void visit(Stmt.Var stmt){
declare(stmt.name);
@ -165,6 +177,13 @@ class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
resolve(expr.value);
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){
if(currentClass == ClassType.NONE){
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){
Token name;
Expr.Variable superclass;
Function[] methods;
mixin defCtorAndAccept;
}

View file

@ -16,12 +16,15 @@ void main(){
"./test/fib_recursive.lox".match(fib(34));
"./test/fib_closure.lox".match(fib(34));
"./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/undefined_var.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/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{

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 @@