Bladeren bron

Add 'this' keyword

runningwater 2 jaren geleden
bovenliggende
commit
8f15896272

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

@@ -4,7 +4,7 @@ import java.util.List;
 
 /**
  * @author GenerateAst
- * @date 2023-08-08 13:53
+ * @date 2023-08-10 15:22
  */
 abstract class Expr {
     interface Visitor<R> {
@@ -24,6 +24,8 @@ abstract class Expr {
 
         R visitSetExpr(Set expr);
 
+        R visitThisExpr(This expr);
+
         R visitUnaryExpr(Unary expr);
 
         R visitVariableExpr(Variable expr);
@@ -155,6 +157,19 @@ abstract class Expr {
         final  Expr value;
     }
 
+    static class This extends Expr {
+        This(Token keyword) {
+            this.keyword = keyword;
+        }
+
+        @Override
+        <R> R accept(Visitor<R> visitor) {
+            return visitor.visitThisExpr(this);
+        }
+
+        final Token keyword;
+    }
+
     static class Unary extends Expr {
         Unary(Token operator, Expr right) {
             this.operator = operator;

+ 13 - 1
src/main/java/com/craftinginterpreters/lox/Interpreter.java

@@ -175,6 +175,11 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
   }
 
   @Override
+  public Object visitThisExpr(Expr.This expr) {
+    return lookUpVariable(expr.keyword, expr);
+  }
+
+  @Override
   public Object visitUnaryExpr(Expr.Unary expr) {
     Object right = evaluate(expr.right);
 
@@ -212,7 +217,14 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
   @Override
   public Void visitClassStmt(Stmt.Class stmt) {
     environment.define(stmt.name.lexeme, null);
-    LoxClass klass = new LoxClass(stmt.name.lexeme);
+
+    Map<String, LoxFunction> methods = new HashMap<>();
+    for (Stmt.Function method : stmt.methods) {
+      LoxFunction function = new LoxFunction(method, environment);
+      methods.put(method.name.lexeme, function);
+    }
+
+    LoxClass klass = new LoxClass(stmt.name.lexeme, methods);
     environment.assign(stmt.name, klass);
     return null;
   }

+ 27 - 16
src/main/java/com/craftinginterpreters/lox/LoxClass.java

@@ -3,6 +3,7 @@
 package com.craftinginterpreters.lox;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author simon
@@ -10,25 +11,35 @@ import java.util.List;
  * @desc
  */
 public class LoxClass implements LoxCallable {
-    final String name;
+  final String name;
+  private final Map<String, LoxFunction> methods;
 
-    public LoxClass(String name) {
-        this.name = name;
-    }
+  public LoxClass(String name, Map<String, LoxFunction> methods) {
+    this.name = name;
+    this.methods = methods;
+  }
 
-    @Override
-    public String toString() {
-        return name;
-    }
+  @Override
+  public String toString() {
+    return name;
+  }
 
-    @Override
-    public int arity() {
-        return 0;
-    }
+  @Override
+  public int arity() {
+    return 0;
+  }
 
-    @Override
-    public Object call(Interpreter interpreter, List<Object> arguments) {
-        LoxInstance instance = new LoxInstance(this);
-        return instance;
+  @Override
+  public Object call(Interpreter interpreter, List<Object> arguments) {
+    LoxInstance instance = new LoxInstance(this);
+    return instance;
+  }
+
+  public LoxFunction findMethod(String name) {
+    if (methods.containsKey(name)) {
+      return methods.get(name);
     }
+
+    return null;
+  }
 }

+ 44 - 37
src/main/java/com/craftinginterpreters/lox/LoxFunction.java

@@ -10,46 +10,53 @@ import java.util.List;
  * @desc
  */
 public class LoxFunction implements LoxCallable {
-    private final Stmt.Function declaration;
-    private final Environment closure;
+  private final Stmt.Function declaration;
+  private final Environment closure;
 
-    public LoxFunction(Stmt.Function declaration, Environment closure) {
-        this.declaration = declaration;
-        this.closure = closure;
-    }
+  public LoxFunction(Stmt.Function declaration, Environment closure) {
+    this.declaration = declaration;
+    this.closure = closure;
+  }
 
-    @Override
-    public int arity() {
-        return declaration.params.size();
-    }
+  @Override
+  public int arity() {
+    return declaration.params.size();
+  }
 
-    @Override
-    public Object call(Interpreter interpreter, List<Object> arguments) {
-        Environment environment = new Environment(closure);
-        for (int i = 0; i < declaration.params.size(); i++) {
-            environment.define(declaration.params.get(i).lexeme, arguments.get(i));
-        }
-        try {
-            interpreter.executeBlock(declaration.body, environment);
-        } catch (Return returnVal) {
-            return returnVal.value;
-        }
-        return null;
+  @Override
+  public Object call(Interpreter interpreter, List<Object> arguments) {
+    Environment environment = new Environment(closure);
+    for (int i = 0; i < declaration.params.size(); i++) {
+      environment.define(declaration.params.get(i).lexeme, arguments.get(i));
     }
-
-    /**
-     * <code>
-     * fun add(a,b) {
-     * print a + b;
-     * }
-     * <p>
-     * print add; // "<fn add>".
-     * </code>
-     *
-     * @return
-     */
-    @Override
-    public String toString() {
-        return "<fn " + declaration.name.lexeme + ">";
+    try {
+      interpreter.executeBlock(declaration.body, environment);
+    } catch (Return returnVal) {
+      return returnVal.value;
     }
+    return null;
+  }
+
+  public LoxFunction bind(LoxInstance instance) {
+    Environment environment = new Environment(closure);
+    environment.define("this", instance);
+
+    return new LoxFunction(declaration, environment);
+  }
+
+  /**
+   * <code>
+   * fun add(a,b) {
+   * print a + b;
+   * }
+   * <p>
+   * print add; // "<fn add>".
+   * </code>
+   *
+   * @return
+   */
+  @Override
+  public String toString() {
+    return "<fn " + declaration.name.lexeme + ">";
+  }
 }

+ 13 - 1
src/main/java/com/craftinginterpreters/lox/LoxInstance.java

@@ -28,11 +28,23 @@ public class LoxInstance {
     return klass.name + " instance";
   }
 
+  /**
+   * get a field -- a bit of state store on the instance
+   *
+   * <p>get a method defined on the instance's class
+   *
+   * @param name
+   * @return
+   */
   public Object get(Token name) {
     if (fields.containsKey(name.lexeme)) {
       return fields.get(name.lexeme);
     }
-    throw new RuntimeError(name, "undefined '" + name.lexeme + "'.");
+
+    LoxFunction method = klass.findMethod(name.lexeme);
+    if (method != null) return method.bind(this);
+
+    throw new RuntimeError(name, "undefined property '" + name.lexeme + "'.");
   }
 
   public void set(Token name, Object value) {

+ 5 - 3
src/main/java/com/craftinginterpreters/lox/Parser.java

@@ -267,8 +267,8 @@ public class Parser {
       if (expr instanceof Expr.Variable) {
         Token name = ((Expr.Variable) expr).name;
         return new Expr.Assign(name, value);
-      }else if (expr instanceof Expr.Get get) {
-          return new Expr.Set(get.object, get.name, value);
+      } else if (expr instanceof Expr.Get get) {
+        return new Expr.Set(get.object, get.name, value);
       }
 
       error(equals, "Invalid assignment target.");
@@ -399,7 +399,7 @@ public class Parser {
   }
 
   //    primary        → NUMBER | STRING | "true" | "false" | "nil"
-  //            | "(" expression ")" | IDENTIFIER ;
+  //            | "(" expression ")" | IDENTIFIER | this ;
   private Expr primary() {
     if (match(FALSE)) return new Expr.Literal(false);
     if (match(TRUE)) return new Expr.Literal(true);
@@ -415,6 +415,8 @@ public class Parser {
       return new Expr.Grouping(expr);
     }
 
+    if (match(THIS)) return new Expr.This(previous());
+
     if (match(IDENTIFIER)) {
       return new Expr.Variable(previous());
     }

+ 222 - 187
src/main/java/com/craftinginterpreters/lox/Resolver.java

@@ -13,220 +13,255 @@ import java.util.Stack;
  * @desc variable resolution
  */
 public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
-    private final Interpreter interpreter;
-    private final Stack<Map<String, Boolean>> scopes = new Stack<>();
-    private FunctionType currentFunction = FunctionType.NONE;
-
-    public Resolver(Interpreter interpreter) {
-        this.interpreter = interpreter;
-    }
-
-    @Override
-    public Void visitAssignExpr(Expr.Assign expr) {
-        resolve(expr.value);
-        resolveLocal(expr, expr.name);
-        return null;
+  private final Interpreter interpreter;
+  private final Stack<Map<String, Boolean>> scopes = new Stack<>();
+  private FunctionType currentFunction = FunctionType.NONE;
+
+  public Resolver(Interpreter interpreter) {
+    this.interpreter = interpreter;
+  }
+
+  @Override
+  public Void visitAssignExpr(Expr.Assign expr) {
+    resolve(expr.value);
+    resolveLocal(expr, expr.name);
+    return null;
+  }
+
+  @Override
+  public Void visitBinaryExpr(Expr.Binary expr) {
+    resolve(expr.left);
+    resolve(expr.right);
+    return null;
+  }
+
+  @Override
+  public Void visitCallExpr(Expr.Call expr) {
+    resolve(expr.callee);
+
+    for (Expr argument : expr.arguments) {
+      resolve(argument);
     }
 
-    @Override
-    public Void visitBinaryExpr(Expr.Binary expr) {
-        resolve(expr.left);
-        resolve(expr.right);
-        return null;
+    return null;
+  }
+
+  @Override
+  public Void visitGetExpr(Expr.Get expr) {
+    resolve(expr.object);
+    return null;
+  }
+
+  @Override
+  public Void visitGroupingExpr(Expr.Grouping expr) {
+    resolve(expr.expression);
+    return null;
+  }
+
+  @Override
+  public Void visitLiteralExpr(Expr.Literal expr) {
+    // A literal expression doesn't mention any variables
+    // and doesn't contain any subexpressions so there is
+    // no work to do.
+    return null;
+  }
+
+  @Override
+  public Void visitLogicalExpr(Expr.Logical expr) {
+    resolve(expr.left);
+    resolve(expr.right);
+    return null;
+  }
+
+  @Override
+  public Void visitSetExpr(Expr.Set expr) {
+    resolve(expr.value);
+    resolve(expr.object);
+    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");
+      return null;
     }
 
-    @Override
-    public Void visitCallExpr(Expr.Call expr) {
-        resolve(expr.callee);
+    resolveLocal(expr, expr.keyword);
+    return null;
+  }
 
-        for (Expr argument : expr.arguments) {
-            resolve(argument);
-        }
+  @Override
+  public Void visitUnaryExpr(Expr.Unary expr) {
+    resolve(expr.right);
+    return null;
+  }
 
-        return null;
+  @Override
+  public Void visitVariableExpr(Expr.Variable expr) {
+    if (!scopes.isEmpty() && scopes.peek().get(expr.name.lexeme) == Boolean.FALSE) {
+      Lox.error(expr.name, "Can't read local variable in its own initializer.");
     }
 
-    @Override
-    public Void visitGetExpr(Expr.Get expr) {
-        resolve(expr.object);
-        return null;
-    }
+    resolveLocal(expr, expr.name);
+    return null;
+  }
 
-    @Override
-    public Void visitGroupingExpr(Expr.Grouping expr) {
-        resolve(expr.expression);
-        return null;
-    }
+  @Override
+  public Void visitBlockStmt(Stmt.Block stmt) {
+    beginScope();
+    resolve(stmt.statements);
+    endScope();
+    return null;
+  }
 
-    @Override
-    public Void visitLiteralExpr(Expr.Literal expr) {
-        // A literal expression doesn't mention any variables
-        // and doesn't contain any subexpressions so there is
-        // no work to do.
-        return null;
-    }
+  @Override
+  public Void visitClassStmt(Stmt.Class stmt) {
+    ClassType enclosingClass = currentClass;
+    currentClass = ClassType.CLASS;
 
-    @Override
-    public Void visitLogicalExpr(Expr.Logical expr) {
-        resolve(expr.left);
-        resolve(expr.right);
-        return null;
-    }
+    declare(stmt.name);
+    define(stmt.name);
 
-    @Override
-    public Void visitSetExpr(Expr.Set expr) {
-        resolve(expr.value);
-        resolve(expr.object);
-        return null;
-    }
+    beginScope();
+    scopes.peek().put("this", true);
 
-    @Override
-    public Void visitUnaryExpr(Expr.Unary expr) {
-        resolve(expr.right);
-        return null;
+    for (Stmt.Function method : stmt.methods) {
+      FunctionType declaration = FunctionType.METHOD;
+      resolveFunction(method, declaration);
     }
-
-    @Override
-    public Void visitVariableExpr(Expr.Variable expr) {
-        if (!scopes.isEmpty() && scopes.peek().get(expr.name.lexeme) == Boolean.FALSE) {
-            Lox.error(expr.name, "Can't read local variable in its own initializer.");
-        }
-
-        resolveLocal(expr, expr.name);
-        return null;
+    endScope();
+
+    currentClass = enclosingClass;
+    return null;
+  }
+
+  @Override
+  public Void visitExpressionStmt(Stmt.Expression stmt) {
+    resolve(stmt.expression);
+    return null;
+  }
+
+  @Override
+  public Void visitFunctionStmt(Stmt.Function stmt) {
+    declare(stmt.name);
+    define(stmt.name);
+
+    resolveFunction(stmt, FunctionType.FUNCTION);
+    return null;
+  }
+
+  @Override
+  public Void visitIfStmt(Stmt.If stmt) {
+    resolve(stmt.condition);
+    resolve(stmt.thenBranch);
+    if (stmt.elseBranch != null) resolve(stmt.elseBranch);
+    return null;
+  }
+
+  @Override
+  public Void visitPrintStmt(Stmt.Print stmt) {
+    resolve(stmt.expression);
+    return null;
+  }
+
+  @Override
+  public Void visitReturnStmt(Stmt.Return stmt) {
+    if (currentFunction == FunctionType.NONE) {
+      Lox.error(stmt.keyword, "Can't return from top-level code");
     }
-
-    @Override
-    public Void visitBlockStmt(Stmt.Block stmt) {
-        beginScope();
-        resolve(stmt.statements);
-        endScope();
-        return null;
+    if (stmt.value != null) resolve(stmt.value);
+    return null;
+  }
+
+  @Override
+  public Void visitVarStmt(Stmt.Var stmt) {
+    // after declaring the variable, we
+    // resolve its initializer expression in
+    // that same scope where the new variable now
+    // exists but is unavailable.
+    declare(stmt.name);
+    if (stmt.initializer != null) {
+      resolve(stmt.initializer);
     }
-
-    @Override
-    public Void visitClassStmt(Stmt.Class stmt) {
-        declare(stmt.name);
-        define(stmt.name);
-        return null;
+    define(stmt.name);
+    return null;
+  }
+
+  @Override
+  public Void visitWhileStmt(Stmt.While stmt) {
+    resolve(stmt.condition);
+    resolve(stmt.body);
+    return null;
+  }
+
+  void resolve(List<Stmt> statements) {
+    for (Stmt statement : statements) {
+      resolve(statement);
     }
-
-    @Override
-    public Void visitExpressionStmt(Stmt.Expression stmt) {
-        resolve(stmt.expression);
-        return null;
+  }
+
+  private void resolve(Stmt stmt) {
+    stmt.accept(this);
+  }
+
+  private void resolve(Expr expr) {
+    expr.accept(this);
+  }
+
+  private void resolveFunction(Stmt.Function function, FunctionType type) {
+    FunctionType enclosingFunction = currentFunction;
+    currentFunction = type;
+    beginScope();
+    for (Token param : function.params) {
+      declare(param);
+      define(param);
     }
+    endScope();
+    currentFunction = enclosingFunction;
+  }
 
-    @Override
-    public Void visitFunctionStmt(Stmt.Function stmt) {
-        declare(stmt.name);
-        define(stmt.name);
+  private void beginScope() {
+    scopes.push(new HashMap<>());
+  }
 
-        resolveFunction(stmt, FunctionType.FUNCTION);
-        return null;
-    }
+  private void endScope() {
+    scopes.pop();
+  }
 
-    @Override
-    public Void visitIfStmt(Stmt.If stmt) {
-        resolve(stmt.condition);
-        resolve(stmt.thenBranch);
-        if (stmt.elseBranch != null) resolve(stmt.elseBranch);
-        return null;
-    }
+  private void declare(Token name) {
+    if (scopes.isEmpty()) return;
 
-    @Override
-    public Void visitPrintStmt(Stmt.Print stmt) {
-        resolve(stmt.expression);
-        return null;
-    }
+    Map<String, Boolean> scope = scopes.peek();
+    scope.put(name.lexeme, false);
+  }
 
-    @Override
-    public Void visitReturnStmt(Stmt.Return stmt) {
-        if (currentFunction == FunctionType.NONE) {
-            Lox.error(stmt.keyword, "Can't return from top-level code");
-        }
-        if (stmt.value != null) resolve(stmt.value);
-        return null;
-    }
+  private void define(Token name) {
+    if (scopes.isEmpty()) return;
 
-    @Override
-    public Void visitVarStmt(Stmt.Var stmt) {
-        // after declaring the variable, we
-        // resolve its initializer expression in
-        // that same scope where the new variable now
-        // exists but is unavailable.
-        declare(stmt.name);
-        if (stmt.initializer != null) {
-            resolve(stmt.initializer);
-        }
-        define(stmt.name);
-        return null;
-    }
+    scopes.peek().put(name.lexeme, true);
+  }
 
-    @Override
-    public Void visitWhileStmt(Stmt.While stmt) {
-        resolve(stmt.condition);
-        resolve(stmt.body);
-        return null;
+  private void resolveLocal(Expr expr, Token name) {
+    for (int i = scopes.size() - 1; i >= 0; i--) {
+      if (scopes.get(i).containsKey(name.lexeme)) {
+        interpreter.resolve(expr, scopes.size() - 1 - i);
+        return;
+      }
     }
+  }
 
-    void resolve(List<Stmt> statements) {
-        for (Stmt statement : statements) {
-            resolve(statement);
-        }
-    }
+  /** fun 类型 */
+  private enum FunctionType {
+    NONE,
+    FUNCTION,
+    METHOD
+  }
 
-    private void resolve(Stmt stmt) {
-        stmt.accept(this);
-    }
-
-    private void resolve(Expr expr) {
-        expr.accept(this);
-    }
-
-    private void resolveFunction(Stmt.Function function, FunctionType type) {
-        FunctionType enclosingFunction = currentFunction;
-        currentFunction = type;
-        beginScope();
-        for (Token param : function.params) {
-            declare(param);
-            define(param);
-        }
-        endScope();
-        currentFunction = enclosingFunction;
-    }
-
-    private void beginScope() {
-        scopes.push(new HashMap<>());
-    }
-
-    private void endScope() {
-        scopes.pop();
-    }
+  private enum ClassType {
+    NONE,
+    CLASS
+  }
 
-    private void declare(Token name) {
-        if (scopes.isEmpty()) return;
-
-        Map<String, Boolean> scope = scopes.peek();
-        scope.put(name.lexeme, false);
-    }
-
-    private void define(Token name) {
-        if (scopes.isEmpty()) return;
-
-        scopes.peek().put(name.lexeme, true);
-    }
-
-    private void resolveLocal(Expr expr, Token name) {
-        for (int i = scopes.size() - 1; i >= 0; i--) {
-            if (scopes.get(i).containsKey(name.lexeme)) {
-                interpreter.resolve(expr, scopes.size() - 1 - i);
-                return;
-            }
-        }
-    }
-
-    private enum FunctionType {
-        NONE, FUNCTION
-    }
+  private ClassType currentClass = ClassType.NONE;
 }

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

@@ -51,6 +51,7 @@ public class GenerateAst {
                 "Literal  : Object value",
                 "Logical  : Expr left, Token operator, Expr right",
                 "Set      : Expr object, Token name, Expr value",
+                "This     : Token keyword",
                 "Unary    : Token operator, Expr right",
                 "Variable : Token name"
         ));

+ 20 - 0
src/test/java/com/craftinginterpreters/lox/LoxTest.java

@@ -1,6 +1,8 @@
 package com.craftinginterpreters.lox;
 
 import java.io.IOException;
+
+import org.junit.Assert;
 import org.junit.Test;
 
 /* Copyright (C) 2019-2023 Hangzhou HSH Co. Ltd.
@@ -13,4 +15,22 @@ public class LoxTest {
         args[0] = LoxTest.class.getClassLoader().getResource ("scope.lox").getFile(); // path
         Lox.main(args);
     }
+
+    @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 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);
+        }
+    }
 }

+ 7 - 0
src/test/resources/class.lox

@@ -0,0 +1,7 @@
+class Bacon {
+    eat() {
+        print "Crunch crunch crunch!";
+    }
+ }
+
+ Bacon.eat();

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

@@ -0,0 +1,19 @@
+class Egotist {
+    speak() {
+        print this;
+    }
+}
+
+var method = Egotist().speak;
+method();
+
+class Cake {
+  taste() {
+    var adjective = "delicious";
+    print "The " + this.flavor + " cake is " + adjective + "!";
+  }
+}
+
+var cake = Cake();
+cake.flavor = "German chocolate";
+cake.taste(); // Prints "The German chocolate cake is delicious!".