Skip to content

Commit

Permalink
feat: add Interpreter class
Browse files Browse the repository at this point in the history
  • Loading branch information
parkerziegler committed Jun 21, 2022
1 parent e3a737f commit 19d5e69
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
@@ -1,4 +1,4 @@
objects = lox/Lox.java lox/Scanner.java lox/Token.java lox/TokenType.java lox/Expr.java lox/Parser.java
objects = lox/*.java

run : $(objects)
javac -d . $(objects)
Expand Down
158 changes: 158 additions & 0 deletions lox/Interpreter.java
@@ -0,0 +1,158 @@
package com.craftinginterpreters.lox;

class Interpreter implements Expr.Visitor<Object> {
void interpret(Expr expression) {
try {
Object value = evaluate(expression);
System.out.println(stringify(value));
} catch (RuntimeError error) {
Lox.runtimeError(error);
}
}

private Object evaluate(Expr expr) {
return expr.accept(this);
}

@Override
public Object visitLiteralExpr(Expr.Literal expr) {
return expr.value;
}

@Override
public Object visitGroupingExpr(Expr.Grouping expr) {
return evaluate(expr.expression);
}

@Override
public Object visitUnaryExpr(Expr.Unary expr) {
// Evaluate the operand first. Since we evaluate the operand subexpression
// before the unary expression, this is a post-order traversal. We do the
// "work" of evaluating subexpressions before the current expression.
Object right = evaluate(expr.right);

// Apply the unary operator to the result of evaluating the operand.
switch (expr.operator.type) {
case BANG:
return !isTruthy(right);
case MINUS:
checkNumberOperand(expr.operator, right);
return -(double) right;
}

// Unreachable.
return null;
}

private void checkNumberOperand(Token operator, Object operand) {
if (operand instanceof Double) {
return;
}

throw new RuntimeError(operator, "Operand must be a number.");
}

@Override
public Object visitBinaryExpr(Expr.Binary expr) {
Object left = evaluate(expr.left);
Object right = evaluate(expr.right);

switch (expr.operator.type) {
case BANG_EQUAL:
return !isEqual(left, right);
case EQUAL_EQUAL:
return isEqual(left, right);
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 MINUS:
checkNumberOperands(expr.operator, left, right);
return (double) left - (double) right;
case PLUS:
// Overload the PLUS operator to handle both addition and String concatenation.
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;
}

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.");
}

// Follow Ruby's rules for falsey values — nil and false are falsey, and all
// other values are truthy.
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;
}

// Ensure we guard against calling .equals on a if it's null.
// This will ensure we don't throw a NullPointerException.
if (a == null) {
return false;
}

return a.equals(b);
}

// The Lox and Java string representations of data are quite similar, with the
// exception of nil vs. null and numbers. We rectify those differences in thie
// method.
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();
}
}
23 changes: 20 additions & 3 deletions lox/Lox.java
Expand Up @@ -10,7 +10,11 @@
import java.util.List;

public class Lox {
// We make the interpreter field static such that successive calls to run
// inside of a REPL session reuse the same interpreter instance.
private static final Interpreter interpreter = new Interpreter();
static boolean hadError = false;
static boolean hadRuntimeError = false;

public static void main(String[] args) throws IOException {
if (args.length > 1) {
Expand All @@ -28,9 +32,15 @@ private static void runFile(String path) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get(path));
run(new String(bytes, Charset.defaultCharset()));

// If we encounter an error, indicate it with exit code.
if (hadError)
// If we encounter a syntax error, indicate it with exit code.
if (hadError) {
System.exit(65);
}

// If we encounter a runtime error, indicate it with exit code.
if (hadRuntimeError) {
System.exit(70);
}
}

// Execute lox code interactively, as you would in a REPL.
Expand Down Expand Up @@ -63,13 +73,20 @@ private static void run(String source) {
return;
}

System.out.println(new AstPrinter().print(expression));
// To see the pretty-printed AST of the expression, uncomment this line.
// System.out.println(new AstPrinter().print(expression));
interpreter.interpret(expression);
}

static void error(int line, String message) {
report(line, "", message);
}

static void runtimeError(RuntimeError error) {
System.err.println(error.getMessage() + "\n[line " + error.token.line + "]");
hadRuntimeError = true;
}

private static void report(int line, String where, String message) {
System.err.println(
"[line " + line + "] Error" + where + ": " + message);
Expand Down
10 changes: 10 additions & 0 deletions lox/RuntimeError.java
@@ -0,0 +1,10 @@
package com.craftinginterpreters.lox;

class RuntimeError extends RuntimeException {
final Token token;

RuntimeError(Token token, String message) {
super(message);
this.token = token;
}
}

0 comments on commit 19d5e69

Please sign in to comment.