Bladeren bron

Inheritance

runningwater 2 jaren geleden
bovenliggende
commit
65ad4aee36

+ 1 - 1
README.md

@@ -111,7 +111,7 @@
                 
                     varDecl  → "var" IDENTIFIER ( "=" expression )? ";" ;
                     returnStmt → "return" expression? ";";
-                    classDecl -> "class" IDENTIFIER "{" function* "}" ;
+                    classDecl -> "class" IDENTIFIER ("<" IDENTIFIER )? "{" function* "}" ;
                     function -> IDENTIFIER "(" parameters? ")" block;
                     parameters -> IDENTIFIER ( "," IDENTIFIER )* ;
                   ```

+ 10 - 0
src/main/java/com/craftinginterpreters/lox/AstPrinter.java

@@ -58,6 +58,16 @@ public class AstPrinter implements Expr.Visitor<String> {
     }
 
     @Override
+    public String visitSuperExpr(Expr.Super expr) {
+        return null;
+    }
+
+    @Override
+    public String visitThisExpr(Expr.This expr) {
+        return null;
+    }
+
+    @Override
     public String visitUnaryExpr(Expr.Unary expr) {
         return parenthesize(expr.operator.lexeme, expr.right);
     }

+ 18 - 1
src/main/java/com/craftinginterpreters/lox/Expr.java

@@ -4,7 +4,7 @@ import java.util.List;
 
 /**
  * @author GenerateAst
- * @date 2023-08-10 15:22
+ * @date 2023-08-15 14:34
  */
 abstract class Expr {
     interface Visitor<R> {
@@ -24,6 +24,8 @@ abstract class Expr {
 
         R visitSetExpr(Set expr);
 
+        R visitSuperExpr(Super expr);
+
         R visitThisExpr(This expr);
 
         R visitUnaryExpr(Unary expr);
@@ -157,6 +159,21 @@ abstract class Expr {
         final  Expr value;
     }
 
+    static class Super extends Expr {
+        Super(Token keyword, Token method) {
+            this.keyword = keyword;
+            this.method = method;
+        }
+
+        @Override
+        <R> R accept(Visitor<R> visitor) {
+            return visitor.visitSuperExpr(this);
+        }
+
+        final Token keyword;
+        final  Token method;
+    }
+
     static class This extends Expr {
         This(Token keyword) {
             this.keyword = keyword;

+ 33 - 3
src/main/java/com/craftinginterpreters/lox/Interpreter.java

@@ -175,6 +175,19 @@ 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
   public Object visitThisExpr(Expr.This expr) {
     return lookUpVariable(expr.keyword, expr);
   }
@@ -216,15 +229,32 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
 
   @Override
   public Void visitClassStmt(Stmt.Class stmt) {
+    Object superclass = null;
+    if (stmt.superClass != null) {
+      superclass = evaluate(stmt.superClass);
+      if (!(superclass instanceof LoxClass)) {
+        throw new RuntimeError(stmt.superClass.name, "Superclass must be a class");
+      }
+    }
+
     environment.define(stmt.name.lexeme, null);
 
+    if (stmt.superClass != null) {
+      environment = new Environment(environment);
+      environment.define("super", superclass);
+    }
+
     Map<String, LoxFunction> methods = new HashMap<>();
     for (Stmt.Function method : stmt.methods) {
-      LoxFunction function = new LoxFunction(method, environment, method.name.lexeme.equals("init"));
+      LoxFunction function =
+          new LoxFunction(method, environment, method.name.lexeme.equals("init"));
       methods.put(method.name.lexeme, function);
     }
 
-    LoxClass klass = new LoxClass(stmt.name.lexeme, methods);
+    LoxClass klass = new LoxClass(stmt.name.lexeme, (LoxClass) superclass, methods);
+    if (superclass != null) {
+      environment = environment.enclosing;
+    }
     environment.assign(stmt.name, klass);
     return null;
   }
@@ -232,7 +262,7 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
   @Override
   public Void visitExpressionStmt(Stmt.Expression stmt) {
     Object value = evaluate(stmt.expression);
-    System.out.println("  " + stringify(value));
+    //System.out.println("  " + stringify(value));
     return null;
   }
 

+ 1 - 1
src/main/java/com/craftinginterpreters/lox/Lox.java

@@ -37,7 +37,7 @@ public class Lox {
      *
      * @param path 文件路径
      */
-    private static void runFile(String path) throws IOException {
+    public static void runFile(String path) throws IOException {
         byte[] bytes = Files.readAllBytes(Path.of(path));
         run(new String(bytes, Charset.defaultCharset()));
 

+ 7 - 1
src/main/java/com/craftinginterpreters/lox/LoxClass.java

@@ -13,9 +13,11 @@ import java.util.Map;
 public class LoxClass implements LoxCallable {
   final String name;
   private final Map<String, LoxFunction> methods;
+  private final LoxClass superclass;
 
-  public LoxClass(String name, Map<String, LoxFunction> methods) {
+  public LoxClass(String name, LoxClass superclass, Map<String, LoxFunction> methods) {
     this.name = name;
+    this.superclass = superclass;
     this.methods = methods;
   }
 
@@ -47,6 +49,10 @@ public class LoxClass implements LoxCallable {
       return methods.get(name);
     }
 
+    if (superclass != null) {
+      return superclass.findMethod(name);
+    }
+
     return null;
   }
 }

+ 29 - 6
src/main/java/com/craftinginterpreters/lox/Parser.java

@@ -182,8 +182,20 @@ public class Parser {
     return new Stmt.Expression(expr);
   }
 
+  /**
+   * classDecl → "class" IDENTIFIER ( "<" IDENTIFIER )? "{" function* "}" ;
+   *
+   * @return {@link Stmt}
+   */
   private Stmt classDeclaration() {
     Token name = consume(IDENTIFIER, "Expect class name.");
+
+    Expr.Variable superClass = null;
+    if (match(LESS)) {
+      consume(IDENTIFIER, "Expect superclass name.");
+      superClass = new Expr.Variable(previous());
+    }
+
     consume(LEFT_BRACE, "Expect '{' before class body.");
 
     List<Stmt.Function> methods = new ArrayList<>();
@@ -193,7 +205,7 @@ public class Parser {
 
     consume(RIGHT_BRACE, "Expect '}' after class body.");
 
-    return new Stmt.Class(name, methods);
+    return new Stmt.Class(name, superClass, methods);
   }
 
   private Stmt.Function function(String kind) {
@@ -362,7 +374,7 @@ public class Parser {
   }
 
   /**
-   * call → primary ( "(" arguments? ")" )* | "." IDENTIFIER;
+   * call → primary ( "(" arguments? ")"  | "." IDENTIFIER )* ;
    *
    * @return Expr
    */
@@ -398,8 +410,13 @@ public class Parser {
     return new Expr.Call(callee, paren, arguments);
   }
 
-  //    primary        → NUMBER | STRING | "true" | "false" | "nil"
-  //            | "(" expression ")" | IDENTIFIER | this ;
+  /**
+   * <pre>{@code primary → "true" | "false" | "nil" | "this"
+   *                       |NUMBER | STRING | IDENTIFIER | "("expression ")"
+   *                       | "super" "." IDENTIFIER ; }
+   *
+   * @return Expr
+   */
   private Expr primary() {
     if (match(FALSE)) return new Expr.Literal(false);
     if (match(TRUE)) return new Expr.Literal(true);
@@ -409,14 +426,20 @@ 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(LEFT_PARAM)) {
       Expr expr = expression();
       consume(RIGHT_PARAM, "Expect ') after expression.");
       return new Expr.Grouping(expr);
     }
 
-    if (match(THIS)) return new Expr.This(previous());
-
     if (match(IDENTIFIER)) {
       return new Expr.Variable(previous());
     }

+ 20 - 0
src/main/java/com/craftinginterpreters/lox/Resolver.java

@@ -81,6 +81,12 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
   }
 
   @Override
+  public Void visitSuperExpr(Expr.Super expr) {
+    resolveLocal(expr, expr.keyword);
+    return null;
+  }
+
+  @Override
   public Void visitThisExpr(Expr.This expr) {
     if (currentClass == ClassType.NONE) {
       Lox.error(expr.keyword, "Can't use 'this' outside of a class");
@@ -123,6 +129,17 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
     declare(stmt.name);
     define(stmt.name);
 
+    if (stmt.superClass != null) {
+      if (stmt.name.lexeme.equals(stmt.superClass.name.lexeme)) {
+        // check weird code, for {@code class Oops < Oops {} } example
+        Lox.error(stmt.superClass.name, "A class can't inherit from itself.");
+      }
+      resolve(stmt.superClass);
+
+      beginScope();
+      scopes.peek().put("super", true);
+    }
+
     beginScope();
     scopes.peek().put("this", true);
 
@@ -135,6 +152,8 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
     }
     endScope();
 
+    if (stmt.superClass != null) endScope();
+
     currentClass = enclosingClass;
     return null;
   }
@@ -225,6 +244,7 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
       declare(param);
       define(param);
     }
+    resolve(function.body);
     endScope();
     currentFunction = enclosingFunction;
   }

+ 33 - 29
src/main/java/com/craftinginterpreters/lox/Stmt.java

@@ -4,12 +4,9 @@ import java.util.List;
 
 /**
  * @author GenerateAst
- * @date 2023-08-03 18:00
+ * @date 2023-08-14 15:46
  */
 abstract class Stmt {
-    abstract <R> R accept(Visitor<R> visitor);
-
-
     interface Visitor<R> {
         R visitBlockStmt(Block stmt);
 
@@ -31,9 +28,8 @@ abstract class Stmt {
 
     }
 
-    static class Block extends Stmt {
-        final List<Stmt> statements;
 
+    static class Block extends Stmt {
         Block(List<Stmt> statements) {
             this.statements = statements;
         }
@@ -42,14 +38,14 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitBlockStmt(this);
         }
+
+        final List<Stmt> statements;
     }
 
     static class Class extends Stmt {
-        final Token name;
-        final List<Stmt.Function> methods;
-
-        Class(Token name, List<Stmt.Function> methods) {
+        Class(Token name, Expr.Variable superClass, List<Stmt.Function> methods) {
             this.name = name;
+            this.superClass = superClass;
             this.methods = methods;
         }
 
@@ -57,11 +53,13 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitClassStmt(this);
         }
+
+        final Token name;
+        final  Expr.Variable superClass;
+        final  List<Stmt.Function> methods;
     }
 
     static class Expression extends Stmt {
-        final Expr expression;
-
         Expression(Expr expression) {
             this.expression = expression;
         }
@@ -70,12 +68,11 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitExpressionStmt(this);
         }
+
+        final Expr expression;
     }
 
     static class Function extends Stmt {
-        final Token name;
-        final List<Token> params;
-        final List<Stmt> body;
         Function(Token name, List<Token> params, List<Stmt> body) {
             this.name = name;
             this.params = params;
@@ -86,12 +83,13 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitFunctionStmt(this);
         }
+
+        final Token name;
+        final  List<Token> params;
+        final  List<Stmt> body;
     }
 
     static class If extends Stmt {
-        final Expr condition;
-        final Stmt thenBranch;
-        final Stmt elseBranch;
         If(Expr condition, Stmt thenBranch, Stmt elseBranch) {
             this.condition = condition;
             this.thenBranch = thenBranch;
@@ -102,11 +100,13 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitIfStmt(this);
         }
+
+        final Expr condition;
+        final  Stmt thenBranch;
+        final  Stmt elseBranch;
     }
 
     static class Print extends Stmt {
-        final Expr expression;
-
         Print(Expr expression) {
             this.expression = expression;
         }
@@ -115,12 +115,11 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitPrintStmt(this);
         }
+
+        final Expr expression;
     }
 
     static class Return extends Stmt {
-        final Token keyword;
-        final Expr value;
-
         Return(Token keyword, Expr value) {
             this.keyword = keyword;
             this.value = value;
@@ -130,12 +129,12 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitReturnStmt(this);
         }
+
+        final Token keyword;
+        final  Expr value;
     }
 
     static class Var extends Stmt {
-        final Token name;
-        final Expr initializer;
-
         Var(Token name, Expr initializer) {
             this.name = name;
             this.initializer = initializer;
@@ -145,12 +144,12 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitVarStmt(this);
         }
+
+        final Token name;
+        final  Expr initializer;
     }
 
     static class While extends Stmt {
-        final Expr condition;
-        final Stmt body;
-
         While(Expr condition, Stmt body) {
             this.condition = condition;
             this.body = body;
@@ -160,5 +159,10 @@ abstract class Stmt {
         <R> R accept(Visitor<R> visitor) {
             return visitor.visitWhileStmt(this);
         }
+
+        final Expr condition;
+        final  Stmt body;
     }
+
+    abstract <R> R accept(Visitor<R> visitor);
 }

+ 2 - 1
src/main/java/com/craftinginterpreters/tool/GenerateAst.java

@@ -51,13 +51,14 @@ public class GenerateAst {
                 "Literal  : Object value",
                 "Logical  : Expr left, Token operator, Expr right",
                 "Set      : Expr object, Token name, Expr value",
+                "Super    : Token keyword, Token method",
                 "This     : Token keyword",
                 "Unary    : Token operator, Expr right",
                 "Variable : Token name"
         ));
 //        defineAst(outputDir, "Stmt", Arrays.asList(
 //                "Block       : List<Stmt> statements",
-//                "Class       : Token name, List<Stmt.Function> methods",
+//                "Class       : Token name, Expr.Variable superClass, List<Stmt.Function> methods",
 //                "Expression  : Expr expression",
 //                "Function    : Token name, List<Token> params, List<Stmt> body",
 //                "If          : Expr condition, Stmt thenBranch, Stmt elseBranch",

+ 24 - 0
src/test/java/com/craftinginterpreters/lox/ClassTest.java

@@ -0,0 +1,24 @@
+/* Copyright (C) 2019-2023 Hangzhou HSH Co. Ltd.
+ * All right reserved.*/ package com.craftinginterpreters.lox;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * @author simon
+ * @date 2023-08-15 10:37
+ * @desc
+ */
+public class ClassTest {
+    @Test
+    public void superclassTest() {
+        String path = LoxTest.class.getClassLoader().getResource("superclass.lox").getFile();
+        try {
+            Lox.runFile(path);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 24 - 21
src/test/java/com/craftinginterpreters/lox/LoxTest.java

@@ -1,36 +1,39 @@
 package com.craftinginterpreters.lox;
 
 import java.io.IOException;
-
-import org.junit.Assert;
 import org.junit.Test;
 
 /* Copyright (C) 2019-2023 Hangzhou HSH Co. Ltd.
  * All right reserved.*/
 public class LoxTest {
 
-    @Test
-    public void mainTest() throws IOException {
-        String[] args = new String[1];
-        args[0] = LoxTest.class.getClassLoader().getResource ("scope.lox").getFile(); // path
-        Lox.main(args);
+  @Test
+  public void mainTest() {
+    String path = LoxTest.class.getClassLoader().getResource("scope.lox").getFile(); // path
+    try {
+      Lox.runFile(path);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
     }
+  }
 
-    @Test
-    public void classTest() throws IOException {
-        String[] args = new String[1];
-        args[0] = LoxTest.class.getClassLoader().getResource("class.lox").getFile();
-        Lox.main(args);
+  @Test
+  public void classTest()  {
+    String path = LoxTest.class.getClassLoader().getResource("class.lox").getFile();
+    try {
+      Lox.runFile(path);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
     }
+  }
 
-    @Test
-    public void thisTest() {
-        String[] args = new String[1];
-        args[0] = LoxTest.class.getClassLoader().getResource("this.lox").getFile();
-        try {
-            Lox.main(args);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+  @Test
+  public void thisTest() {
+    String path = LoxTest.class.getClassLoader().getResource("this.lox").getFile();
+    try {
+      Lox.runFile(path);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
     }
+  }
 }

+ 18 - 6
src/test/resources/class.lox

@@ -1,7 +1,19 @@
-class Bacon {
-    eat() {
-        print "Crunch crunch crunch!";
-    }
- }
+class Bagel {}
+var bagel = Bagel();
+print bagel; // Prints "Bagel instance".
 
- Bacon.eat();
+
+class Person {
+  sayName() {
+    print this.name;
+  }
+}
+
+var jane = Person();
+jane.name = "Jane";
+
+var bill = Person();
+bill.name = "Bill";
+
+bill.sayName = jane.sayName;
+bill.sayName(); // ?

+ 19 - 0
src/test/resources/superclass.lox

@@ -0,0 +1,19 @@
+class A {
+  method() {
+    print "A method";
+  }
+}
+
+class B < A {
+  method() {
+    print "B method";
+  }
+
+  test() {
+    super.method();
+  }
+}
+
+class C < B {}
+
+C().test();

+ 19 - 10
src/test/resources/this.lox

@@ -1,12 +1,3 @@
-class Egotist {
-    speak() {
-        print this;
-    }
-}
-
-var method = Egotist().speak;
-method();
-
 class Cake {
   taste() {
     var adjective = "delicious";
@@ -18,6 +9,14 @@ var cake = Cake();
 cake.flavor = "German chocolate";
 cake.taste(); // Prints "The German chocolate cake is delicious!".
 
+class Egotist {
+    speak() {
+        print this;
+    }
+}
+
+var method = Egotist().speak;
+method();
 
 class Foo {
   init() {
@@ -26,4 +25,14 @@ class Foo {
 }
 
 var foo = Foo();
-print foo.init();
+print foo.init();
+
+
+class Egotist {
+  speak() {
+    print this;
+  }
+}
+
+var method = Egotist().speak;
+method();