From 68a2ebd34fc94488e89ffb82b359ec6e7e152ae9 Mon Sep 17 00:00:00 2001 From: Tom Willemse Date: Thu, 8 Jul 2021 00:14:31 -0700 Subject: Restructure project to make room for clox --- .../com/craftinginterpreters/lox/Interpreter.java | 392 +++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 jlox/src/com/craftinginterpreters/lox/Interpreter.java (limited to 'jlox/src/com/craftinginterpreters/lox/Interpreter.java') diff --git a/jlox/src/com/craftinginterpreters/lox/Interpreter.java b/jlox/src/com/craftinginterpreters/lox/Interpreter.java new file mode 100644 index 0000000..3f4d385 --- /dev/null +++ b/jlox/src/com/craftinginterpreters/lox/Interpreter.java @@ -0,0 +1,392 @@ +package com.craftinginterpreters.lox; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class Interpreter implements Expr.Visitor, Stmt.Visitor { + final Environment globals = new Environment(); + private Environment environment = globals; + private final Map locals = new HashMap<>(); + + Interpreter() { + globals.define("clock", new LoxCallable() { + @Override + public int arity() { + return 0; + } + + @Override + public Object call(Interpreter interpreter, List arguments) { + return (double) System.currentTimeMillis() / 1000.0; + } + + @Override + public String toString() { + return ""; + } + }); + } + + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @Override + public Object visitLogicalExpr(Expr.Logical expr) { + Object left = evaluate(expr.left); + + if (expr.operator.type == TokenType.OR) { + if (isTruthy(left)) + return left; + } else { + if (!isTruthy(left)) + return left; + } + + return evaluate(expr.right); + } + + @Override + public Object visitSetExpr(Expr.Set expr) { + Object object = evaluate(expr.object); + + if (!(object instanceof LoxInstance)) { + throw new RuntimeError(expr.name, "Only instances have fields."); + } + + Object value = evaluate(expr.value); + ((LoxInstance) object).set(expr.name, value); + return value; + } + + @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); + } + + @Override + public Object visitUnaryExpr(Expr.Unary expr) { + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + case BANG: + return !isTruthy(right); + case MINUS: + checkNumberOperand(expr.operator, right); + return -(double) right; + } + + // Unreachable. + return null; + } + + @Override + public Object visitVariableExpr(Expr.Variable expr) { + return lookUpVariable(expr.name, expr); + } + + private Object lookUpVariable(Token name, Expr expr) { + Integer distance = locals.get(expr); + if (distance != null) { + return environment.getAt(distance, name.lexeme); + } else { + return globals.get(name); + } + } + + private void checkNumberOperand(Token operator, Object operand) { + if (operand instanceof Double) + return; + throw new RuntimeError(operator, "Operand must be a number."); + } + + private void checkNumberOperands(Token operator, Object left, Object right) { + if (left instanceof Double && right instanceof Double) + return; + + throw new RuntimeError(operator, "Operands must be numbers."); + } + + private boolean isTruthy(Object object) { + if (object == null) + return false; + if (object instanceof Boolean) + return (boolean) object; + return true; + } + + private boolean isEqual(Object a, Object b) { + if (a == null && b == null) + return true; + if (a == null) + return false; + + return a.equals(b); + } + + private String stringify(Object object) { + if (object == null) + return "nil"; + + if (object instanceof Double) { + String text = object.toString(); + if (text.endsWith(".0")) { + text = text.substring(0, text.length() - 2); + } + return text; + } + + return object.toString(); + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + private void execute(Stmt stmt) { + stmt.accept(this); + } + + public void resolve(Expr expr, int depth) { + locals.put(expr, depth); + } + + public void executeBlock(List statements, Environment environment) { + Environment previous = this.environment; + + try { + this.environment = environment; + + for (Stmt statement : statements) { + execute(statement); + } + } finally { + this.environment = previous; + } + } + + @Override + public Void visitBlockStmt(Stmt.Block stmt) { + executeBlock(stmt.statements, new Environment(environment)); + return null; + } + + @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 methods = new HashMap<>(); + for (Stmt.Function method : stmt.methods) { + LoxFunction function = new LoxFunction(method, environment, method.name.lexeme.equals("init")); + methods.put(method.name.lexeme, function); + } + + LoxClass klass = new LoxClass(stmt.name.lexeme, (LoxClass) superclass, methods); + + if (superclass != null) { + environment = environment.enclosing; + } + + environment.assign(stmt.name, klass); + return null; + } + + @Override + public Void visitExpressionStmt(Stmt.Expression stmt) { + evaluate(stmt.expression); + return null; + } + + @Override + public Void visitFunctionStmt(Stmt.Function stmt) { + LoxFunction function = new LoxFunction(stmt, environment, false); + environment.define(stmt.name.lexeme, function); + return null; + } + + @Override + public Void visitIfStmt(Stmt.If stmt) { + if (isTruthy(evaluate(stmt.condition))) { + execute(stmt.thenBranch); + } else if (stmt.elseBranch != null) { + execute(stmt.elseBranch); + } + + return null; + } + + @Override + public Void visitPrintStmt(Stmt.Print stmt) { + Object value = evaluate(stmt.expression); + System.out.println(stringify(value)); + return null; + } + + @Override + public Void visitReturnStmt(Stmt.Return stmt) { + Object value = null; + if (stmt.value != null) + value = evaluate(stmt.value); + + throw new Return(value); + } + + @Override + public Void visitVarStmt(Stmt.Var stmt) { + Object value = null; + if (stmt.initializer != null) { + value = evaluate(stmt.initializer); + } + + environment.define(stmt.name.lexeme, value); + return null; + } + + @Override + public Void visitWhileStmt(Stmt.While stmt) { + while (isTruthy(evaluate(stmt.condition))) { + execute(stmt.body); + } + + return null; + } + + @Override + public Object visitAssignExpr(Expr.Assign expr) { + Object value = evaluate(expr.value); + + Integer distance = locals.get(expr); + if (distance != null) { + environment.assignAt(distance, expr.name, value); + } else { + globals.assign(expr.name, value); + } + + return value; + } + + @Override + public Object visitBinaryExpr(Expr.Binary expr) { + Object left = evaluate(expr.left); + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + case GREATER: + checkNumberOperands(expr.operator, left, right); + return (double) left > (double) right; + case GREATER_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double) left >= (double) right; + case LESS: + checkNumberOperands(expr.operator, left, right); + return (double) left < (double) right; + case LESS_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double) left <= (double) right; + case BANG_EQUAL: + return !isEqual(left, right); + case EQUAL_EQUAL: + return isEqual(left, right); + case MINUS: + checkNumberOperands(expr.operator, left, right); + return (double) left - (double) right; + case PLUS: + if (left instanceof Double && right instanceof Double) { + return (double) left + (double) right; + } + + if (left instanceof String && right instanceof String) { + return (String) left + (String) right; + } + + throw new RuntimeError(expr.operator, "Operands must be two numbers or two strings."); + case SLASH: + checkNumberOperands(expr.operator, left, right); + return (double) left / (double) right; + case STAR: + checkNumberOperands(expr.operator, left, right); + return (double) left * (double) right; + } + + // Unreachable. + return null; + } + + @Override + public Object visitCallExpr(Expr.Call expr) { + Object callee = evaluate(expr.callee); + + List arguments = new ArrayList<>(); + for (Expr argument : expr.arguments) { + arguments.add(evaluate(argument)); + } + + if (!(callee instanceof LoxCallable)) { + throw new RuntimeError(expr.paren, "Can only call functions and classes."); + } + + LoxCallable function = (LoxCallable) callee; + if (arguments.size() != function.arity()) { + throw new RuntimeError(expr.paren, + "Expected " + function.arity() + " arguments but got " + arguments.size() + "."); + } + + return function.call(this, arguments); + } + + @Override + public Object visitGetExpr(Expr.Get expr) { + Object object = evaluate(expr.object); + if (object instanceof LoxInstance) { + return ((LoxInstance) object).get(expr.name); + } + + throw new RuntimeError(expr.name, "Only instances have properties."); + } + + public void interpret(List statements) { + try { + for (Stmt statement : statements) { + execute(statement); + } + } catch (RuntimeError error) { + Lox.runtimeError(error); + } + } +} -- cgit v1.2.3-54-g00ecf