Inheritance 13
This commit is contained in:
parent
d8ac625429
commit
848c846e09
12 changed files with 117 additions and 7 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,4 +16,5 @@ lox-test-*
|
||||||
*.lst
|
*.lst
|
||||||
|
|
||||||
.msc/
|
.msc/
|
||||||
|
test/test.lox
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
3
test/err/super_outside_class.lox
Normal file
3
test/err/super_outside_class.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
super.notEvenInAClass();
|
||||||
|
|
||||||
8
test/err/super_without_superclass.lox
Normal file
8
test/err/super_without_superclass.lox
Normal 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
36
test/super.lox
Normal 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();
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue