Implement super

This commit is contained in:
ctsk
2022-10-11 13:18:40 +02:00
parent f0b55491d8
commit da00410738
6 changed files with 67 additions and 6 deletions

View File

@@ -79,6 +79,11 @@ public class AstPrinter {
return wrap("=", left + "." + expr.name().lexeme(), val); return wrap("=", left + "." + expr.name().lexeme(), val);
} }
@Override
public String visitSuperExpr(Expr.Super expr) {
return "super";
}
@Override @Override
public String visitThisExpr(Expr.This expr) { public String visitThisExpr(Expr.This expr) {
return "this"; return "this";
@@ -162,6 +167,11 @@ public class AstPrinter {
return left + "." + expr.name().lexeme() + " = " + val; return left + "." + expr.name().lexeme() + " = " + val;
} }
@Override
public String visitSuperExpr(Expr.Super expr) {
return "super";
}
@Override @Override
public String visitThisExpr(Expr.This expr) { public String visitThisExpr(Expr.This expr) {
return "this"; return "this";

View File

@@ -5,7 +5,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class Environment { public class Environment {
private final Environment enclosing; final Environment enclosing;
private final Map<String, Object> values = new HashMap<>(); private final Map<String, Object> values = new HashMap<>();
Environment() { Environment() {

View File

@@ -90,6 +90,11 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
environment.define(stmt.name().lexeme(), null); environment.define(stmt.name().lexeme(), null);
if (stmt.superclass() != null) {
environment = new Environment(environment);
environment.define("super", superclass);
}
Map<String, LoxFunction> methods = new HashMap<>(); Map<String, LoxFunction> methods = new HashMap<>();
for (var method : stmt.methods()) { for (var method : stmt.methods()) {
var function = new LoxFunction(method, environment, method.name().lexeme().equals("init")); var function = new LoxFunction(method, environment, method.name().lexeme().equals("init"));
@@ -97,6 +102,11 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
} }
LoxClass clazz = new LoxClass(stmt.name().lexeme(), (LoxClass) superclass, methods); LoxClass clazz = new LoxClass(stmt.name().lexeme(), (LoxClass) superclass, methods);
if (superclass != null) {
environment = environment.enclosing;
}
environment.assign(stmt.name(), clazz); environment.assign(stmt.name(), clazz);
return null; return null;
} }
@@ -263,6 +273,21 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
} }
} }
@Override
public Object visitSuperExpr(Expr.Super expr) {
int distance = locals.get(expr);
LoxClass superclass = (LoxClass) environment.getAt(distance, "super");
LoxInstance object = (LoxInstance) environment.getAt(distance - 1, "this");
LoxFunction method = superclass.findMethod(expr.method().lexeme());
if (method == null) {
throw new RuntimeError(expr.method(),
"Undefined property '" + expr.method().lexeme() + "'.");
}
return method.bind(object);
}
@Override @Override
public Object visitThisExpr(Expr.This expr) { public Object visitThisExpr(Expr.This expr) {
return lookupVariable(expr.keyword(), expr); return lookupVariable(expr.keyword(), expr);

View File

@@ -50,10 +50,9 @@ import static xyz.ctsk.lox.TokenType.*;
* | call ; * | call ;
* call → primary ( "(" arguments? ")" | "." IDENTIFIER )* ; * call → primary ( "(" arguments? ")" | "." IDENTIFIER )* ;
* arguments → expression ( "," expression )* ; * arguments → expression ( "," expression )* ;
* primary → NUMBER | STRING | "true" | "false" | "nil" * primary → "true" | "false" | "nil" | "this"
* | "(" expression ")" * | NUMBER | STRING | IDENTIFIER | "(" expression ")"
* | IDENTIFIER * | "super" "." IDENTIFIER ;
* | THIS ;
*/ */
public class Parser { public class Parser {
private final List<Token> tokens; private final List<Token> tokens;
@@ -396,6 +395,13 @@ public class Parser {
return new Expr.Literal(previous().literal()); return new Expr.Literal(previous().literal());
} }
if (match(SUPER)) {
Token keyword = previous();
consume(DOT, "Expect ',' after 'super'.");
Token method = consume(IDENTIFIER, "Expect superclass method name.");
return new Expr.Super(keyword, method);
}
if (match(THIS)) return new Expr.This(previous()); if (match(THIS)) return new Expr.This(previous());
if (match(IDENTIFIER)) { if (match(IDENTIFIER)) {

View File

@@ -10,7 +10,7 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
private ClassType currentClass = ClassType.NONE; private ClassType currentClass = ClassType.NONE;
private enum ClassType { NONE, CLASS } private enum ClassType { NONE, CLASS, SUBCLASS }
Resolver(Interpreter interpreter) { Resolver(Interpreter interpreter) {
this.interpreter = interpreter; this.interpreter = interpreter;
@@ -90,12 +90,18 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
define(stmt.name()); define(stmt.name());
if (stmt.superclass() != null) { if (stmt.superclass() != null) {
currentClass = ClassType.SUBCLASS;
if (stmt.name().lexeme().equals(stmt.superclass().name().lexeme())) { if (stmt.name().lexeme().equals(stmt.superclass().name().lexeme())) {
Lox.error(stmt.superclass().name(), "A class can't inherit from itself."); Lox.error(stmt.superclass().name(), "A class can't inherit from itself.");
} }
resolve(stmt.superclass()); resolve(stmt.superclass());
} }
if (stmt.superclass() != null) {
beginScope();
scopes.peek().put("super", true);
}
beginScope(); beginScope();
scopes.peek().put("this", true); scopes.peek().put("this", true);
@@ -111,6 +117,8 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
endScope(); endScope();
if (stmt.superclass() != null) endScope();
currentClass = enclosing; currentClass = enclosing;
return null; return null;
} }
@@ -229,6 +237,17 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
return null; return null;
} }
@Override
public Void visitSuperExpr(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 subclass.");
}
resolveLocal(expr, expr.keyword());
return null;
}
@Override @Override
public Void visitThisExpr(Expr.This expr) { public Void visitThisExpr(Expr.This expr) {
if (currentClass == ClassType.NONE) { if (currentClass == ClassType.NONE) {

View File

@@ -9,6 +9,7 @@
@Rule(head = "Literal", body = {"Object value"}), @Rule(head = "Literal", body = {"Object value"}),
@Rule(head = "Logical", body = {"Expr left", "Token operator", "Expr right"}), @Rule(head = "Logical", body = {"Expr left", "Token operator", "Expr right"}),
@Rule(head = "Set", body = {"Expr object", "Token name", "Expr value"}), @Rule(head = "Set", body = {"Expr object", "Token name", "Expr value"}),
@Rule(head = "Super", body = {"Token keyword", "Token method"}),
@Rule(head = "This", body = {"Token keyword"}), @Rule(head = "This", body = {"Token keyword"}),
@Rule(head = "Unary", body = {"Token operator", "Expr right"}), @Rule(head = "Unary", body = {"Token operator", "Expr right"}),
@Rule(head = "Variable", body = {"Token name"}) @Rule(head = "Variable", body = {"Token name"})