Skip to content

Commit fd90ef9

Browse files
authored
Chapter 7: Evaluating Expressions (#4)
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.
1 parent 9508c9d commit fd90ef9

File tree

6 files changed

+263
-45
lines changed

6 files changed

+263
-45
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Each commit corresponds to one chapter in the book:
77

88
* [Chapter 4](https://github.com/jeschkies/lox-rs/commit/9fef15e73fdf57a3e428bb074059c7e144e257f7)
99
* [Chapter 5](https://github.com/jeschkies/lox-rs/commit/0156a95b4bf448dbff9cb4341a2339b741a163ca)
10+
* [Chapter 6](https://github.com/jeschkies/lox-rs/commit/9508c9d887a88540597d314520ae6aa045256e7d)

src/error.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use std::convert;
12
use std::fmt;
3+
use std::io;
24

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

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

2224
#[derive(Debug)]
2325
pub enum Error {
26+
Io(io::Error),
2427
Parse,
28+
Runtime { token: Token, message: String },
2529
}
2630

2731
impl fmt::Display for Error {
2832
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2933
match self {
34+
Error::Io(underlying) => write!(f, "IoError {}", underlying),
3035
Error::Parse => write!(f, "ParseError"),
36+
Error::Runtime { message, .. } => write!(f, "RuntimeError {}", message),
3137
}
3238
}
3339
}
40+
41+
impl std::error::Error for Error {
42+
fn description(&self) -> &str {
43+
"Lox Error"
44+
}
45+
}
46+
47+
impl convert::From<io::Error> for Error {
48+
fn from(e: io::Error) -> Self {
49+
Error::Io(e)
50+
}
51+
}

src/interpreter.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::error::Error;
2+
use crate::syntax::{Expr, LiteralValue, Visitor};
3+
use crate::token::{Token, TokenType};
4+
5+
/// A simple representation of an Lox object akin to a Java `Object`.
6+
enum Object {
7+
Boolean(bool),
8+
Null,
9+
Number(f64),
10+
String(String),
11+
}
12+
13+
impl Object {
14+
fn equals(&self, other: &Object) -> bool {
15+
match (self, other) {
16+
(Object::Null, Object::Null) => true,
17+
(_, Object::Null) => false,
18+
(Object::Null, _) => false,
19+
(Object::Boolean(left), Object::Boolean(right)) => left == right,
20+
(Object::Number(left), Object::Number(right)) => left == right,
21+
(Object::String(left), Object::String(right)) => left.eq(right),
22+
_ => false,
23+
}
24+
}
25+
}
26+
27+
pub struct Interpreter;
28+
29+
impl Interpreter {
30+
pub fn interpret(&self, expression: &Expr) -> Result<String, Error> {
31+
self.evaluate(expression).map(|value| self.stringify(value))
32+
}
33+
34+
fn evaluate(&self, expression: &Expr) -> Result<Object, Error> {
35+
expression.accept(self)
36+
}
37+
38+
fn is_truthy(&self, object: &Object) -> bool {
39+
match object {
40+
Object::Null => false,
41+
Object::Boolean(b) => b.clone(),
42+
_ => true,
43+
}
44+
}
45+
46+
fn is_equal(&self, left: &Object, right: &Object) -> bool {
47+
left.equals(right)
48+
}
49+
50+
fn stringify(&self, object: Object) -> String {
51+
match object {
52+
Object::Null => "nil".to_string(),
53+
Object::Number(n) => n.to_string(),
54+
Object::Boolean(b) => b.to_string(),
55+
Object::String(s) => s,
56+
}
57+
}
58+
59+
/// Equivalent to checkNumberOperands
60+
fn number_operand_error<R>(&self, operator: &Token) -> Result<R, Error> {
61+
Err(Error::Runtime {
62+
token: operator.clone(),
63+
message: "Operand must be a number.".to_string(),
64+
})
65+
}
66+
}
67+
68+
impl Visitor<Object> for Interpreter {
69+
fn visit_binary_expr(
70+
&self,
71+
left: &Expr,
72+
operator: &Token,
73+
right: &Expr,
74+
) -> Result<Object, Error> {
75+
let l = self.evaluate(left)?;
76+
let r = self.evaluate(right)?;
77+
78+
match &operator.tpe {
79+
TokenType::Greater => match (l, r) {
80+
(Object::Number(left_number), Object::Number(right_number)) => {
81+
Ok(Object::Boolean(left_number > right_number))
82+
}
83+
_ => self.number_operand_error(operator),
84+
},
85+
TokenType::GreaterEqual => match (l, r) {
86+
(Object::Number(left_number), Object::Number(right_number)) => {
87+
Ok(Object::Boolean(left_number >= right_number))
88+
}
89+
_ => self.number_operand_error(operator),
90+
},
91+
TokenType::Less => match (l, r) {
92+
(Object::Number(left_number), Object::Number(right_number)) => {
93+
Ok(Object::Boolean(left_number < right_number))
94+
}
95+
_ => self.number_operand_error(operator),
96+
},
97+
TokenType::LessEqual => match (l, r) {
98+
(Object::Number(left_number), Object::Number(right_number)) => {
99+
Ok(Object::Boolean(left_number <= right_number))
100+
}
101+
_ => self.number_operand_error(operator),
102+
},
103+
TokenType::Minus => match (l, r) {
104+
(Object::Number(left_number), Object::Number(right_number)) => {
105+
Ok(Object::Number(left_number - right_number))
106+
}
107+
_ => self.number_operand_error(operator),
108+
},
109+
TokenType::Plus => match (l, r) {
110+
(Object::Number(left_number), Object::Number(right_number)) => {
111+
Ok(Object::Number(left_number + right_number))
112+
}
113+
(Object::String(left_string), Object::String(right_string)) => {
114+
Ok(Object::String(left_string.clone() + &right_string))
115+
}
116+
_ => Err(Error::Runtime {
117+
token: operator.clone(),
118+
message: "Operands must be two numbers or two strings.".to_string(),
119+
}),
120+
},
121+
TokenType::Slash => match (l, r) {
122+
(Object::Number(left_number), Object::Number(right_number)) => {
123+
Ok(Object::Number(left_number / right_number))
124+
}
125+
_ => self.number_operand_error(operator),
126+
},
127+
TokenType::Star => match (l, r) {
128+
(Object::Number(left_number), Object::Number(right_number)) => {
129+
Ok(Object::Number(left_number * right_number))
130+
}
131+
_ => self.number_operand_error(operator),
132+
},
133+
TokenType::BangEqual => Ok(Object::Boolean(!self.is_equal(&l, &r))),
134+
TokenType::EqualEqual => Ok(Object::Boolean(self.is_equal(&l, &r))),
135+
_ => unreachable!(),
136+
}
137+
}
138+
139+
fn visit_grouping_expr(&self, expr: &Expr) -> Result<Object, Error> {
140+
self.evaluate(expr)
141+
}
142+
143+
fn visit_literal_expr(&self, value: &LiteralValue) -> Result<Object, Error> {
144+
match value {
145+
LiteralValue::Boolean(b) => Ok(Object::Boolean(b.clone())),
146+
LiteralValue::Null => Ok(Object::Null),
147+
LiteralValue::Number(n) => Ok(Object::Number(n.clone())),
148+
LiteralValue::String(s) => Ok(Object::String(s.clone())),
149+
}
150+
}
151+
152+
fn visit_unary_expr(&self, operator: &Token, right: &Expr) -> Result<Object, Error> {
153+
let right = self.evaluate(right)?;
154+
155+
match &operator.tpe {
156+
TokenType::Minus => match right {
157+
Object::Number(n) => Ok(Object::Number(-n.clone())),
158+
_ => self.number_operand_error(operator),
159+
},
160+
TokenType::Bang => Ok(Object::Boolean(!self.is_truthy(&right))), // TODO: is_truthy could simply return an Object.
161+
_ => unreachable!(), // TODO: fail if right is not a number.
162+
}
163+
}
164+
}

src/main.rs

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod error;
2+
mod interpreter;
23
mod parser;
34
mod scanner;
45
mod syntax;
@@ -8,45 +9,64 @@ use std::io::{self, BufRead};
89
use std::process::exit;
910
use std::{env, fs};
1011

12+
use error::Error;
13+
use interpreter::Interpreter;
1114
use parser::Parser;
1215
use scanner::Scanner;
1316
use syntax::AstPrinter;
1417

15-
fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
16-
let args: Vec<String> = env::args().collect();
17-
match args.as_slice() {
18-
[_, file] => run_file(file)?,
19-
[_] => run_prompt()?,
20-
_ => {
21-
eprintln!("Usage: lox-rs [script]");
22-
exit(64)
18+
struct Lox {
19+
interpreter: Interpreter,
20+
}
21+
22+
impl Lox {
23+
fn new() -> Self {
24+
Lox {
25+
interpreter: Interpreter,
2326
}
2427
}
25-
Ok(())
26-
}
2728

28-
fn run_file(path: &str) -> io::Result<()> {
29-
let source = fs::read_to_string(path)?;
30-
run(source)
31-
}
29+
fn run_file(&self, path: &str) -> Result<(), Error> {
30+
let source = fs::read_to_string(path)?;
31+
self.run(source)
32+
}
33+
34+
fn run_prompt(&self) -> Result<(), Error> {
35+
let stdin = io::stdin();
36+
for line in stdin.lock().lines() {
37+
self.run(line?); // Ignore error.
38+
print!("> ");
39+
}
40+
Ok(())
41+
}
3242

33-
fn run_prompt() -> io::Result<()> {
34-
let stdin = io::stdin();
35-
for line in stdin.lock().lines() {
36-
run(line?); // Ignore error.
37-
print!("> ");
43+
fn run(&self, source: String) -> Result<(), Error> {
44+
let mut scanner = Scanner::new(source);
45+
let tokens = scanner.scan_tokens();
46+
47+
let mut parser = Parser::new(tokens);
48+
if let Some(expression) = parser.parse() {
49+
println!("{}", self.interpreter.interpret(&expression)?);
50+
}
51+
Ok(())
3852
}
39-
Ok(())
4053
}
4154

42-
fn run(source: String) -> io::Result<()> {
43-
let mut scanner = Scanner::new(source);
44-
let tokens = scanner.scan_tokens();
45-
46-
let mut parser = Parser::new(tokens);
47-
if let Some(expression) = parser.parse() {
48-
let printer = AstPrinter;
49-
println!("{}", printer.print(expression));
55+
fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
56+
let args: Vec<String> = env::args().collect();
57+
let lox = Lox::new();
58+
match args.as_slice() {
59+
[_, file] => match lox.run_file(file) {
60+
Ok(_) => (),
61+
Err(Error::Runtime { .. }) => exit(70),
62+
Err(Error::Parse) => exit(65),
63+
Err(Error::Io(_)) => unimplemented!(),
64+
},
65+
[_] => lox.run_prompt()?,
66+
_ => {
67+
eprintln!("Usage: lox-rs [script]");
68+
exit(64)
69+
}
5070
}
5171
Ok(())
5272
}

src/parser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,6 @@ mod tests {
232232
let expression = parser.parse().expect("Could not parse sample code.");
233233
let printer = AstPrinter;
234234

235-
assert_eq!(printer.print(expression), "(* (- 123) 45.67)");
235+
assert_eq!(printer.print(expression).unwrap(), "(* (- 123) 45.67)");
236236
}
237237
}

0 commit comments

Comments
 (0)