From da004107382c4623934a4c5a75dcf165862b88e4 Mon Sep 17 00:00:00 2001 From: ctsk <9384305+ctsk@users.noreply.github.com> Date: Tue, 11 Oct 2022 13:18:40 +0200 Subject: [PATCH] Implement super --- .../main/java/xyz/ctsk/lox/AstPrinter.java | 10 ++++++++ .../main/java/xyz/ctsk/lox/Environment.java | 2 +- .../main/java/xyz/ctsk/lox/Interpreter.java | 25 +++++++++++++++++++ .../src/main/java/xyz/ctsk/lox/Parser.java | 14 ++++++++--- .../src/main/java/xyz/ctsk/lox/Resolver.java | 21 +++++++++++++++- .../main/java/xyz/ctsk/lox/package-info.java | 1 + 6 files changed, 67 insertions(+), 6 deletions(-) diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java b/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java index dd55bb0..efbac84 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java @@ -79,6 +79,11 @@ public class AstPrinter { return wrap("=", left + "." + expr.name().lexeme(), val); } + @Override + public String visitSuperExpr(Expr.Super expr) { + return "super"; + } + @Override public String visitThisExpr(Expr.This expr) { return "this"; @@ -162,6 +167,11 @@ public class AstPrinter { return left + "." + expr.name().lexeme() + " = " + val; } + @Override + public String visitSuperExpr(Expr.Super expr) { + return "super"; + } + @Override public String visitThisExpr(Expr.This expr) { return "this"; diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java index ed55b46..25d6c1f 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Optional; public class Environment { - private final Environment enclosing; + final Environment enclosing; private final Map values = new HashMap<>(); Environment() { diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java index dc32f56..c38c3e7 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java @@ -90,6 +90,11 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { environment.define(stmt.name().lexeme(), null); + if (stmt.superclass() != null) { + environment = new Environment(environment); + environment.define("super", superclass); + } + Map methods = new HashMap<>(); for (var method : stmt.methods()) { var function = new LoxFunction(method, environment, method.name().lexeme().equals("init")); @@ -97,6 +102,11 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { } LoxClass clazz = new LoxClass(stmt.name().lexeme(), (LoxClass) superclass, methods); + + if (superclass != null) { + environment = environment.enclosing; + } + environment.assign(stmt.name(), clazz); return null; } @@ -263,6 +273,21 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { } } + @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 public Object visitThisExpr(Expr.This expr) { return lookupVariable(expr.keyword(), expr); diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java index 2fe849c..2d7f422 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java @@ -50,10 +50,9 @@ import static xyz.ctsk.lox.TokenType.*; * | call ; * call → primary ( "(" arguments? ")" | "." IDENTIFIER )* ; * arguments → expression ( "," expression )* ; - * primary → NUMBER | STRING | "true" | "false" | "nil" - * | "(" expression ")" - * | IDENTIFIER - * | THIS ; + * primary → "true" | "false" | "nil" | "this" + * | NUMBER | STRING | IDENTIFIER | "(" expression ")" + * | "super" "." IDENTIFIER ; */ public class Parser { private final List tokens; @@ -396,6 +395,13 @@ public class Parser { 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(IDENTIFIER)) { diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Resolver.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Resolver.java index d38604e..b6f2a7d 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Resolver.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Resolver.java @@ -10,7 +10,7 @@ public class Resolver implements Expr.Visitor, Stmt.Visitor { private ClassType currentClass = ClassType.NONE; - private enum ClassType { NONE, CLASS } + private enum ClassType { NONE, CLASS, SUBCLASS } Resolver(Interpreter interpreter) { this.interpreter = interpreter; @@ -90,12 +90,18 @@ public class Resolver implements Expr.Visitor, Stmt.Visitor { define(stmt.name()); if (stmt.superclass() != null) { + currentClass = ClassType.SUBCLASS; if (stmt.name().lexeme().equals(stmt.superclass().name().lexeme())) { Lox.error(stmt.superclass().name(), "A class can't inherit from itself."); } resolve(stmt.superclass()); } + if (stmt.superclass() != null) { + beginScope(); + scopes.peek().put("super", true); + } + beginScope(); scopes.peek().put("this", true); @@ -111,6 +117,8 @@ public class Resolver implements Expr.Visitor, Stmt.Visitor { endScope(); + if (stmt.superclass() != null) endScope(); + currentClass = enclosing; return null; } @@ -229,6 +237,17 @@ public class Resolver implements Expr.Visitor, Stmt.Visitor { 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 public Void visitThisExpr(Expr.This expr) { if (currentClass == ClassType.NONE) { diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java b/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java index 77f7d2f..111cbff 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java @@ -9,6 +9,7 @@ @Rule(head = "Literal", body = {"Object value"}), @Rule(head = "Logical", body = {"Expr left", "Token operator", "Expr right"}), @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 = "Unary", body = {"Token operator", "Expr right"}), @Rule(head = "Variable", body = {"Token name"})