Skip to content

Commit

Permalink
Chapter 7: Evaluating Expressions (#4)
Browse files Browse the repository at this point in the history
This implements [chapter 7](http://craftinginterpreters.com/evaluating-expressions.html).
We introduce a simple evaluation of some basic operators.

The biggest difference to the original Java version is that we are not setting `hadRuntimeError`
but return a `Result<String, Error>` from the `interpret` function.

The Java version also casts the literal to `String` or `Double` and handles exceptions. In Rust
we can simply pattern match and return a `Error::Runtime` when `+` is called for other types.
  • Loading branch information
jeschkies authored Nov 11, 2019
1 parent 9508c9d commit fd90ef9
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 45 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Each commit corresponds to one chapter in the book:

* [Chapter 4](https://github.com/jeschkies/lox-rs/commit/9fef15e73fdf57a3e428bb074059c7e144e257f7)
* [Chapter 5](https://github.com/jeschkies/lox-rs/commit/0156a95b4bf448dbff9cb4341a2339b741a163ca)
* [Chapter 6](https://github.com/jeschkies/lox-rs/commit/9508c9d887a88540597d314520ae6aa045256e7d)
18 changes: 18 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::convert;
use std::fmt;
use std::io;

use crate::token::{Token, TokenType};

Expand All @@ -21,13 +23,29 @@ pub fn parser_error(token: &Token, message: &str) {

#[derive(Debug)]
pub enum Error {
Io(io::Error),
Parse,
Runtime { token: Token, message: String },
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(underlying) => write!(f, "IoError {}", underlying),
Error::Parse => write!(f, "ParseError"),
Error::Runtime { message, .. } => write!(f, "RuntimeError {}", message),
}
}
}

impl std::error::Error for Error {
fn description(&self) -> &str {
"Lox Error"
}
}

impl convert::From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
164 changes: 164 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use crate::error::Error;
use crate::syntax::{Expr, LiteralValue, Visitor};
use crate::token::{Token, TokenType};

/// A simple representation of an Lox object akin to a Java `Object`.
enum Object {
Boolean(bool),
Null,
Number(f64),
String(String),
}

impl Object {
fn equals(&self, other: &Object) -> bool {
match (self, other) {
(Object::Null, Object::Null) => true,
(_, Object::Null) => false,
(Object::Null, _) => false,
(Object::Boolean(left), Object::Boolean(right)) => left == right,
(Object::Number(left), Object::Number(right)) => left == right,
(Object::String(left), Object::String(right)) => left.eq(right),
_ => false,
}
}
}

pub struct Interpreter;

impl Interpreter {
pub fn interpret(&self, expression: &Expr) -> Result<String, Error> {
self.evaluate(expression).map(|value| self.stringify(value))
}

fn evaluate(&self, expression: &Expr) -> Result<Object, Error> {
expression.accept(self)
}

fn is_truthy(&self, object: &Object) -> bool {
match object {
Object::Null => false,
Object::Boolean(b) => b.clone(),
_ => true,
}
}

fn is_equal(&self, left: &Object, right: &Object) -> bool {
left.equals(right)
}

fn stringify(&self, object: Object) -> String {
match object {
Object::Null => "nil".to_string(),
Object::Number(n) => n.to_string(),
Object::Boolean(b) => b.to_string(),
Object::String(s) => s,
}
}

/// Equivalent to checkNumberOperands
fn number_operand_error<R>(&self, operator: &Token) -> Result<R, Error> {
Err(Error::Runtime {
token: operator.clone(),
message: "Operand must be a number.".to_string(),
})
}
}

impl Visitor<Object> for Interpreter {
fn visit_binary_expr(
&self,
left: &Expr,
operator: &Token,
right: &Expr,
) -> Result<Object, Error> {
let l = self.evaluate(left)?;
let r = self.evaluate(right)?;

match &operator.tpe {
TokenType::Greater => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Boolean(left_number > right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::GreaterEqual => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Boolean(left_number >= right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::Less => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Boolean(left_number < right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::LessEqual => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Boolean(left_number <= right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::Minus => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Number(left_number - right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::Plus => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Number(left_number + right_number))
}
(Object::String(left_string), Object::String(right_string)) => {
Ok(Object::String(left_string.clone() + &right_string))
}
_ => Err(Error::Runtime {
token: operator.clone(),
message: "Operands must be two numbers or two strings.".to_string(),
}),
},
TokenType::Slash => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Number(left_number / right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::Star => match (l, r) {
(Object::Number(left_number), Object::Number(right_number)) => {
Ok(Object::Number(left_number * right_number))
}
_ => self.number_operand_error(operator),
},
TokenType::BangEqual => Ok(Object::Boolean(!self.is_equal(&l, &r))),
TokenType::EqualEqual => Ok(Object::Boolean(self.is_equal(&l, &r))),
_ => unreachable!(),
}
}

fn visit_grouping_expr(&self, expr: &Expr) -> Result<Object, Error> {
self.evaluate(expr)
}

fn visit_literal_expr(&self, value: &LiteralValue) -> Result<Object, Error> {
match value {
LiteralValue::Boolean(b) => Ok(Object::Boolean(b.clone())),
LiteralValue::Null => Ok(Object::Null),
LiteralValue::Number(n) => Ok(Object::Number(n.clone())),
LiteralValue::String(s) => Ok(Object::String(s.clone())),
}
}

fn visit_unary_expr(&self, operator: &Token, right: &Expr) -> Result<Object, Error> {
let right = self.evaluate(right)?;

match &operator.tpe {
TokenType::Minus => match right {
Object::Number(n) => Ok(Object::Number(-n.clone())),
_ => self.number_operand_error(operator),
},
TokenType::Bang => Ok(Object::Boolean(!self.is_truthy(&right))), // TODO: is_truthy could simply return an Object.
_ => unreachable!(), // TODO: fail if right is not a number.
}
}
}
76 changes: 48 additions & 28 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod error;
mod interpreter;
mod parser;
mod scanner;
mod syntax;
Expand All @@ -8,45 +9,64 @@ use std::io::{self, BufRead};
use std::process::exit;
use std::{env, fs};

use error::Error;
use interpreter::Interpreter;
use parser::Parser;
use scanner::Scanner;
use syntax::AstPrinter;

fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
let args: Vec<String> = env::args().collect();
match args.as_slice() {
[_, file] => run_file(file)?,
[_] => run_prompt()?,
_ => {
eprintln!("Usage: lox-rs [script]");
exit(64)
struct Lox {
interpreter: Interpreter,
}

impl Lox {
fn new() -> Self {
Lox {
interpreter: Interpreter,
}
}
Ok(())
}

fn run_file(path: &str) -> io::Result<()> {
let source = fs::read_to_string(path)?;
run(source)
}
fn run_file(&self, path: &str) -> Result<(), Error> {
let source = fs::read_to_string(path)?;
self.run(source)
}

fn run_prompt(&self) -> Result<(), Error> {
let stdin = io::stdin();
for line in stdin.lock().lines() {
self.run(line?); // Ignore error.
print!("> ");
}
Ok(())
}

fn run_prompt() -> io::Result<()> {
let stdin = io::stdin();
for line in stdin.lock().lines() {
run(line?); // Ignore error.
print!("> ");
fn run(&self, source: String) -> Result<(), Error> {
let mut scanner = Scanner::new(source);
let tokens = scanner.scan_tokens();

let mut parser = Parser::new(tokens);
if let Some(expression) = parser.parse() {
println!("{}", self.interpreter.interpret(&expression)?);
}
Ok(())
}
Ok(())
}

fn run(source: String) -> io::Result<()> {
let mut scanner = Scanner::new(source);
let tokens = scanner.scan_tokens();

let mut parser = Parser::new(tokens);
if let Some(expression) = parser.parse() {
let printer = AstPrinter;
println!("{}", printer.print(expression));
fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
let args: Vec<String> = env::args().collect();
let lox = Lox::new();
match args.as_slice() {
[_, file] => match lox.run_file(file) {
Ok(_) => (),
Err(Error::Runtime { .. }) => exit(70),
Err(Error::Parse) => exit(65),
Err(Error::Io(_)) => unimplemented!(),
},
[_] => lox.run_prompt()?,
_ => {
eprintln!("Usage: lox-rs [script]");
exit(64)
}
}
Ok(())
}
2 changes: 1 addition & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,6 @@ mod tests {
let expression = parser.parse().expect("Could not parse sample code.");
let printer = AstPrinter;

assert_eq!(printer.print(expression), "(* (- 123) 45.67)");
assert_eq!(printer.print(expression).unwrap(), "(* (- 123) 45.67)");
}
}
Loading

0 comments on commit fd90ef9

Please sign in to comment.