Exercise: Expression Evaluation
Let’s write a simple recursive evaluator for arithmetic expressions.
An example of a small arithmetic expression could be 10 + 20, which evaluates
to 30. We can represent the expression as a tree:
A bigger and more complex expression would be (10 * 9) + ((3 - 4) * 5), which
evaluates to 85. We represent this as a much bigger tree:
In code, we will represent the tree with two types:
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
Add,
Sub,
Mul,
Div,
}
/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
/// An operation on two subexpressions.
Op { op: Operation, left: Box<Expression>, right: Box<Expression> },
/// A literal value
Value(i64),
}
The Box type here is a smart pointer, and will be covered in detail later in
the course. An expression can be “boxed” with Box::new as seen in the tests.
To evaluate a boxed expression, use the deref operator (*) to “unbox” it:
eval(*boxed_expr).
Create a new Cargo library project with
cargo new --lib evaluator
Copy and paste the code below into a the src/lib.rs file.
Then begin implementing eval. Use cargo test to ensure that the final
library passes the tests. It may be helpful to use todo!() and get the tests
to pass one-by-one. You can also skip a test temporarily with #[ignore]:
#[test]
#[ignore]
fn test_value() { .. }
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
Add,
Sub,
Mul,
Div,
}
/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
/// An operation on two subexpressions.
Op { op: Operation, left: Box<Expression>, right: Box<Expression> },
/// A literal value
Value(i64),
}
fn eval(e: Expression) -> i64 {
todo!()
}
#[test]
fn test_value() {
assert_eq!(eval(Expression::Value(19)), 19);
}
#[test]
fn test_sum() {
assert_eq!(
eval(Expression::Op {
op: Operation::Add,
left: Box::new(Expression::Value(10)),
right: Box::new(Expression::Value(20)),
}),
30
);
}
#[test]
fn test_recursion() {
let term1 = Expression::Op {
op: Operation::Mul,
left: Box::new(Expression::Value(10)),
right: Box::new(Expression::Value(9)),
};
let term2 = Expression::Op {
op: Operation::Mul,
left: Box::new(Expression::Op {
op: Operation::Sub,
left: Box::new(Expression::Value(3)),
right: Box::new(Expression::Value(4)),
}),
right: Box::new(Expression::Value(5)),
};
assert_eq!(
eval(Expression::Op {
op: Operation::Add,
left: Box::new(term1),
right: Box::new(term2),
}),
85
);
}
#[test]
fn test_zeros() {
assert_eq!(
eval(Expression::Op {
op: Operation::Add,
left: Box::new(Expression::Value(0)),
right: Box::new(Expression::Value(0))
}),
0
);
assert_eq!(
eval(Expression::Op {
op: Operation::Mul,
left: Box::new(Expression::Value(0)),
right: Box::new(Expression::Value(0))
}),
0
);
assert_eq!(
eval(Expression::Op {
op: Operation::Sub,
left: Box::new(Expression::Value(0)),
right: Box::new(Expression::Value(0))
}),
0
);
}
#[test]
fn test_div() {
assert_eq!(
eval(Expression::Op {
op: Operation::Div,
left: Box::new(Expression::Value(10)),
right: Box::new(Expression::Value(2)),
}),
5
)
}