[jlox] Classes
This commit is contained in:
@@ -33,6 +33,12 @@ public class AstPrinter {
|
||||
return reverse ? wrap(left, right, op) : wrap(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitGetExpr(Expr.Get expr) {
|
||||
var left = expr.object().accept(this);
|
||||
return left + "." + expr.name().lexeme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitCallExpr(Expr.Call expr) {
|
||||
var fun = expr.callee().accept(this);
|
||||
@@ -64,6 +70,20 @@ public class AstPrinter {
|
||||
return reverse ? wrap(left, right, op) : wrap(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitSetExpr(Expr.Set expr) {
|
||||
|
||||
|
||||
var left = expr.object().accept(this);
|
||||
var val = expr.object().accept(this);
|
||||
return wrap("=", left + "." + expr.name().lexeme(), val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitThisExpr(Expr.This expr) {
|
||||
return "this";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitUnaryExpr(Expr.Unary expr) {
|
||||
var op = expr.operator().lexeme();
|
||||
@@ -102,6 +122,12 @@ public class AstPrinter {
|
||||
expr.right().accept(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitGetExpr(Expr.Get expr) {
|
||||
var left = expr.object().accept(this);
|
||||
return left + "." + expr.name().lexeme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitCallExpr(Expr.Call expr) {
|
||||
var fun = expr.callee().accept(this);
|
||||
@@ -129,6 +155,18 @@ public class AstPrinter {
|
||||
expr.right().accept(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitSetExpr(Expr.Set expr) {
|
||||
var left = expr.object().accept(this);
|
||||
var val = expr.object().accept(this);
|
||||
return left + "." + expr.name().lexeme() + " = " + val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitThisExpr(Expr.This expr) {
|
||||
return "this";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitUnaryExpr(Expr.Unary expr) {
|
||||
return expr.operator().lexeme() + expr.right().accept(this);
|
||||
|
||||
@@ -3,5 +3,5 @@ package xyz.ctsk.lox;
|
||||
public enum FunctionType {
|
||||
NONE,
|
||||
FUNCTION,
|
||||
METHOD
|
||||
INITIALIZER, METHOD
|
||||
}
|
||||
|
||||
@@ -78,6 +78,21 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClassStmt(Stmt.Class stmt) {
|
||||
environment.define(stmt.name().lexeme(), null);
|
||||
|
||||
Map<String, LoxFunction> methods = new HashMap<>();
|
||||
for (var method : stmt.methods()) {
|
||||
var function = new LoxFunction(method, environment, method.name().lexeme().equals("init"));
|
||||
methods.put(method.name().lexeme(), function);
|
||||
}
|
||||
|
||||
LoxClass clazz = new LoxClass(stmt.name().lexeme(), methods);
|
||||
environment.assign(stmt.name(), clazz);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
||||
evaluate(stmt.expression());
|
||||
@@ -86,7 +101,7 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||
|
||||
@Override
|
||||
public Void visitFunctionStmt(Stmt.Function stmt) {
|
||||
environment.define(stmt.name().lexeme(), new LoxFunction(stmt, environment));
|
||||
environment.define(stmt.name().lexeme(), new LoxFunction(stmt, environment, false));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -177,6 +192,15 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitGetExpr(Expr.Get expr) {
|
||||
var object = evaluate(expr.object());
|
||||
if (object instanceof LoxInstance loxInstance) {
|
||||
return loxInstance.get(expr.name());
|
||||
}
|
||||
throw new RuntimeError(expr.name(), "Only instances have properties.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitCallExpr(Expr.Call expr) {
|
||||
var callee = evaluate(expr.callee());
|
||||
@@ -219,6 +243,23 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||
return evaluate(expr.right());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitSetExpr(Expr.Set expr) {
|
||||
var object = evaluate(expr.object());
|
||||
if (object instanceof LoxInstance instance) {
|
||||
var value = evaluate(expr.value());
|
||||
instance.set(expr.name(), value);
|
||||
return value;
|
||||
} else {
|
||||
throw new RuntimeError(expr.name(), "Only instances have fields.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitThisExpr(Expr.This expr) {
|
||||
return lookupVariable(expr.keyword(), expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitUnaryExpr(Expr.Unary expr) {
|
||||
var right = evaluate(expr.right());
|
||||
@@ -235,7 +276,7 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||
return lookupVariable(expr.name(), expr);
|
||||
}
|
||||
|
||||
private Object lookupVariable(Token name, Expr.Variable expr) {
|
||||
private Object lookupVariable(Token name, Expr expr) {
|
||||
var distance = locals.get(expr);
|
||||
if (distance != null) {
|
||||
return environment.getAt(distance, name.lexeme());
|
||||
|
||||
44
jlox/lox/src/main/java/xyz/ctsk/lox/LoxClass.java
Normal file
44
jlox/lox/src/main/java/xyz/ctsk/lox/LoxClass.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package xyz.ctsk.lox;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LoxClass implements LoxCallable {
|
||||
final String name;
|
||||
private final Map<String, LoxFunction> methods;
|
||||
|
||||
LoxClass(String name, Map<String, LoxFunction> methods) {
|
||||
this.name = name;
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arity() {
|
||||
LoxFunction initializer = findMethod("init");
|
||||
if (initializer == null) return 0;
|
||||
return initializer.arity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Interpreter interpreter, List<Object> arguments) {
|
||||
var instance = new LoxInstance(this);
|
||||
LoxFunction initializer = findMethod("init");
|
||||
if (initializer != null) {
|
||||
initializer.bind(instance).call(interpreter, arguments);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
LoxFunction findMethod(String name) {
|
||||
if (methods.containsKey(name)) {
|
||||
return methods.get(name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,12 @@ public class LoxFunction implements LoxCallable {
|
||||
private final Stmt.Function declaration;
|
||||
private final Environment closure;
|
||||
|
||||
public LoxFunction(Stmt.Function declaration, Environment closure) {
|
||||
private final boolean isInitializer;
|
||||
|
||||
public LoxFunction(Stmt.Function declaration, Environment closure, boolean isInitializer) {
|
||||
this.declaration = declaration;
|
||||
this.closure = closure;
|
||||
this.isInitializer = isInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,11 +30,20 @@ public class LoxFunction implements LoxCallable {
|
||||
try {
|
||||
interpreter.executeBlock(declaration.body(), environment);
|
||||
} catch (Return ret) {
|
||||
if (isInitializer) return closure.getAt(0, "this");
|
||||
return ret.value;
|
||||
}
|
||||
|
||||
if (isInitializer) return closure.getAt(0, "this");
|
||||
return null;
|
||||
}
|
||||
|
||||
LoxFunction bind(LoxInstance instance) {
|
||||
Environment env = new Environment(closure);
|
||||
env.define("this", instance);
|
||||
return new LoxFunction(declaration, env, isInitializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<fn " + declaration.name().lexeme() + " >";
|
||||
|
||||
33
jlox/lox/src/main/java/xyz/ctsk/lox/LoxInstance.java
Normal file
33
jlox/lox/src/main/java/xyz/ctsk/lox/LoxInstance.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package xyz.ctsk.lox;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LoxInstance {
|
||||
private LoxClass clazz;
|
||||
private final Map<String, Object> fields = new HashMap<>();
|
||||
|
||||
LoxInstance(LoxClass clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
Object get(Token name) {
|
||||
if (fields.containsKey(name.lexeme())) {
|
||||
return fields.get(name.lexeme());
|
||||
}
|
||||
|
||||
LoxFunction method = clazz.findMethod(name.lexeme());
|
||||
if (method != null) return method.bind(this);
|
||||
|
||||
throw new RuntimeError(name, "Undefined property '%s'.".formatted(name.lexeme()));
|
||||
}
|
||||
|
||||
void set(Token name, Object value) {
|
||||
fields.put(name.lexeme(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<" + clazz.name + " instance>";
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@ import static xyz.ctsk.lox.TokenType.*;
|
||||
* A recursive descent parser for the following grammar:
|
||||
* <p>
|
||||
* program → declaration* EOF ;
|
||||
* declaration → funDecl
|
||||
* declaration → classDecl
|
||||
* | funDecl
|
||||
* | varDecl
|
||||
* | statement ;
|
||||
* classDecl → "class" IDENTIFIER "{" function* "}" ;
|
||||
* funDecl → "fun" function ;
|
||||
* function → IDENTIFIER "(" parameters? ")" block ;
|
||||
* parameters → IDENTIFIER ( "," IDENTIFIER )* ;
|
||||
@@ -35,7 +37,7 @@ import static xyz.ctsk.lox.TokenType.*;
|
||||
* exprStmt → expression ";" ;
|
||||
* expression → equality ;
|
||||
* expression → assignment ;
|
||||
* assignment → IDENTIFIER "=" assignment
|
||||
* assignment → ( call "." )? IDENTIFIER "=" assignment
|
||||
* | logic_or ;
|
||||
* logic_or → logic_and ( "or" logic_and )* ;
|
||||
* logic_and → equality ( "and" equality )* ;
|
||||
@@ -45,11 +47,12 @@ import static xyz.ctsk.lox.TokenType.*;
|
||||
* factor → unary ( ( "/" | "*" ) unary )* ;
|
||||
* unary → ( "!" | "-" ) unary
|
||||
* | call ;
|
||||
* call → primary ( "(" arguments? ")" )* ;
|
||||
* call → primary ( "(" arguments? ")" | "." IDENTIFIER )* ;
|
||||
* arguments → expression ( "," expression )* ;
|
||||
* primary → NUMBER | STRING | "true" | "false" | "nil"
|
||||
* | "(" expression ")"
|
||||
* | IDENTIFIER;
|
||||
* | IDENTIFIER
|
||||
* | THIS ;
|
||||
*/
|
||||
public class Parser {
|
||||
private final List<Token> tokens;
|
||||
@@ -72,6 +75,7 @@ public class Parser {
|
||||
|
||||
private Stmt declaration() {
|
||||
try {
|
||||
if (match(CLASS)) return classDeclaration();
|
||||
if (match(FUN)) return function(FunctionType.FUNCTION);
|
||||
if (match(VAR)) return varDeclaration();
|
||||
return match(VAR) ? varDeclaration() : statement();
|
||||
@@ -81,6 +85,21 @@ public class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private Stmt classDeclaration() {
|
||||
var name = consume(IDENTIFIER, "Expect class name.");
|
||||
consume(LEFT_BRACE, "Expect '{' before class body.");
|
||||
|
||||
List<Stmt.Function> methods = new ArrayList<>();
|
||||
|
||||
while (!check(RIGHT_BRACE) && !isAtEnd()) {
|
||||
methods.add(function(FunctionType.METHOD));
|
||||
}
|
||||
|
||||
consume(LEFT_BRACE, "Expect '}' after class body.");
|
||||
return new Stmt.Class(name, methods);
|
||||
|
||||
}
|
||||
|
||||
private Stmt.Function function(FunctionType kind) {
|
||||
Token name = consume(IDENTIFIER, "Expect %s name.".formatted(kind.toString().toLowerCase()));
|
||||
|
||||
@@ -232,6 +251,8 @@ public class Parser {
|
||||
|
||||
if (expr instanceof Expr.Variable varExpr) {
|
||||
return new Expr.Assign(varExpr.name(), value);
|
||||
} else if (expr instanceof Expr.Get getExpr) {
|
||||
return new Expr.Set(getExpr.object(), getExpr.name(), value);
|
||||
} else {
|
||||
//We report an error but don't throw it because we do not need our parser to panic and sync.
|
||||
//noinspection ThrowableNotThrown
|
||||
@@ -330,7 +351,9 @@ public class Parser {
|
||||
while(true) {
|
||||
if (match(LEFT_PAREN)) {
|
||||
expr = finishCall(expr);
|
||||
} else {
|
||||
} else if (match(DOT)) {
|
||||
Token name = consume(IDENTIFIER, "Expect property name after '.'.");
|
||||
expr = new Expr.Get(expr, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -365,6 +388,8 @@ public class Parser {
|
||||
return new Expr.Literal(previous().literal());
|
||||
}
|
||||
|
||||
if (match(THIS)) return new Expr.This(previous());
|
||||
|
||||
if (match(IDENTIFIER)) {
|
||||
return new Expr.Variable(previous());
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||
private final Stack<Map<String, Boolean>> scopes = new Stack<>();
|
||||
private FunctionType currentFuntion = FunctionType.NONE;
|
||||
|
||||
private enum ClassType { NONE, CLASS }
|
||||
|
||||
private ClassType currentClass = ClassType.NONE;
|
||||
|
||||
|
||||
Resolver(Interpreter interpreter) {
|
||||
this.interpreter = interpreter;
|
||||
@@ -82,6 +86,31 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClassStmt(Stmt.Class stmt) {
|
||||
var enclosing = currentClass;
|
||||
currentClass = ClassType.CLASS;
|
||||
declare(stmt.name());
|
||||
define(stmt.name());
|
||||
beginScope();
|
||||
scopes.peek().put("this", true);
|
||||
|
||||
for (var method : stmt.methods()) {
|
||||
FunctionType declaration = FunctionType.METHOD;
|
||||
|
||||
if (method.name().lexeme().equals("init")) {
|
||||
declaration = FunctionType.INITIALIZER;
|
||||
}
|
||||
|
||||
resolveFunction(method, declaration);
|
||||
}
|
||||
|
||||
endScope();
|
||||
|
||||
currentClass = enclosing;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
||||
@@ -118,6 +147,9 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||
}
|
||||
|
||||
if (stmt.value() != null) {
|
||||
if (currentFuntion == FunctionType.INITIALIZER) {
|
||||
Lox.error(stmt.keyword(), "Can't return a value from an initializer.");
|
||||
}
|
||||
resolve(stmt.value());
|
||||
}
|
||||
return null;
|
||||
@@ -155,6 +187,12 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitGetExpr(Expr.Get expr) {
|
||||
resolve(expr.object());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Expr.Call expr) {
|
||||
resolve(expr.callee());
|
||||
@@ -180,6 +218,24 @@ public class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||
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;
|
||||
}
|
||||
|
||||
resolveLocal(expr, expr.keyword());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnaryExpr(Expr.Unary expr) {
|
||||
resolve(expr.right());
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
rules = {
|
||||
@Rule(head = "Assign", body = {"Token name", "Expr value"}),
|
||||
@Rule(head = "Binary", body = {"Expr left", "Token operator", "Expr right"}),
|
||||
@Rule(head = "Get", body = {"Expr object", "Token name"}),
|
||||
@Rule(head = "Call", body = {"Expr callee", "Token paren", "List<Expr> arguments"}),
|
||||
@Rule(head = "Grouping", body = {"Expr expression"}),
|
||||
@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 = "This", body = {"Token keyword"}),
|
||||
@Rule(head = "Unary", body = {"Token operator", "Expr right"}),
|
||||
@Rule(head = "Variable", body = {"Token name"})
|
||||
}),
|
||||
@Root(name = "Stmt",
|
||||
rules = {
|
||||
@Rule(head = "Block", body = {"List<Stmt> statements"}),
|
||||
@Rule(head = "Class", body = {"Token name", "List<Stmt.Function> methods"}),
|
||||
@Rule(head = "Expression", body = {"Expr expression"}),
|
||||
@Rule(head = "Function", body = {"Token name", "List<Token> params", "List<Stmt> body"}),
|
||||
@Rule(head = "If", body = {"Expr condition", "Stmt thenBranch", "Stmt elseBranch"}),
|
||||
|
||||
Reference in New Issue
Block a user