Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e3a737f
commit 19d5e69
Showing
4 changed files
with
189 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |