diff --git a/.gitignore b/.gitignore index a8ba56d..5a66296 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ lox-test-* *.lst .msc/ +test/test.lox diff --git a/src/jlox/expr.d b/src/jlox/expr.d index 2c68f8b..f95a4ff 100644 --- a/src/jlox/expr.d +++ b/src/jlox/expr.d @@ -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; diff --git a/src/jlox/interpreter.d b/src/jlox/interpreter.d index 319c62c..15870bc 100644 --- a/src/jlox/interpreter.d +++ b/src/jlox/interpreter.d @@ -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); } diff --git a/src/jlox/loxclass.d b/src/jlox/loxclass.d index da8a202..44be38a 100644 --- a/src/jlox/loxclass.d +++ b/src/jlox/loxclass.d @@ -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; } diff --git a/src/jlox/parser.d b/src/jlox/parser.d index eea1900..a314992 100644 --- a/src/jlox/parser.d +++ b/src/jlox/parser.d @@ -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 { diff --git a/src/jlox/resolver.d b/src/jlox/resolver.d index b5cc879..c9dbf81 100644 --- a/src/jlox/resolver.d +++ b/src/jlox/resolver.d @@ -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."); diff --git a/src/jlox/stmt.d b/src/jlox/stmt.d index 3d42392..64d35ae 100644 --- a/src/jlox/stmt.d +++ b/src/jlox/stmt.d @@ -36,6 +36,7 @@ abstract class Stmt{ } static class Class : typeof(this){ Token name; + Expr.Variable superclass; Function[] methods; mixin defCtorAndAccept; } diff --git a/test/all.d b/test/all.d index e891ff1..a7c3542 100755 --- a/test/all.d +++ b/test/all.d @@ -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{ diff --git a/test/err/super_outside_class.lox b/test/err/super_outside_class.lox new file mode 100644 index 0000000..4a14239 --- /dev/null +++ b/test/err/super_outside_class.lox @@ -0,0 +1,3 @@ + +super.notEvenInAClass(); + diff --git a/test/err/super_without_superclass.lox b/test/err/super_without_superclass.lox new file mode 100644 index 0000000..d6b4cdc --- /dev/null +++ b/test/err/super_without_superclass.lox @@ -0,0 +1,8 @@ + +class Eclair{ + cook(){ + super.cook(); + print "Pipe full of crème pâtissière."; + } +} + diff --git a/test/super.lox b/test/super.lox new file mode 100644 index 0000000..dec5f8d --- /dev/null +++ b/test/super.lox @@ -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(); + diff --git a/test/test.lox b/test/test.lox deleted file mode 100644 index 139597f..0000000 --- a/test/test.lox +++ /dev/null @@ -1,2 +0,0 @@ - -