Crafting Interpreters in Rust: Parsing Binary Operators, part 3
So far, we implemented parsing for numbers, strings, booleans, nil, unary operators ! and -, multiplication and
division, and addition and subtraction. Next on the list are comparison operators (greater-than, less-than, etc.) and
equality operators.
As a reminder, the grammar for expressions, from the book, looks like this:
expression → equality ;
equality → comparison ( ( "!=" | "==" ) comparison )* ;
comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
term → factor ( ( "-" | "+" ) factor )* ;
factor → unary ( ( "/" | "*" ) unary )* ;
unary → ( "!" | "-" ) unary
| primary ;
primary → NUMBER | STRING | "true" | "false" | "nil"
| "(" expression ")" ;
Comparison operators
As before, I wrote the tests first, but you’ll have to take my word for it. They’re squashed into commit 7512035056.
Here’s an example:
#[test]
fn compare_gt_true() {
let parser = lox::ExpressionParser::new();
let expr = parser.parse("12 > 3").unwrap();
assert_eq!(
expr,
Expression::BinaryOperation {
left: Box::new(Expression::Number(12.0)),
op: BinaryOperator::Gt,
right: Box::new(Expression::Number(3.0))
}
);
assert_eq!(evaluate(expr), Expression::Bool(true));
}
The implementation is relatively simple, now we’ve got our BinaryOperation macro. It looks like this:
pub Expression = Comparison;
// comparison : comparison ( ">" | ">=" | "<" | "<=" ) term
// | term
Comparison = BinaryOperation<Comparison, ComparisonOp, Term, Term>;
ComparisonOp: BinaryOperator = {
">" => BinaryOperator::Gt,
">=" => BinaryOperator::Gte,
"<" => BinaryOperator::Lt,
"<=" => BinaryOperator::Lte,
};
…and we add Gt, Gte, etc. members to the BinaryOperator enum. Implementing evaluate is relatively simple, too:
// ...
BinaryOperator::Gt => Expression::Bool(left > right),
BinaryOperator::Gte => Expression::Bool(left >= right),
BinaryOperator::Lt => Expression::Bool(left < right),
BinaryOperator::Lte => Expression::Bool(left <= right),
Equality operators
These are also relatively easy; see commit f2031aa4f9.
Here’s one of the tests:
#[test]
fn equal_when_equal() {
let parser = lox::ExpressionParser::new();
let expr = parser.parse("12 == 12").unwrap();
assert_eq!(
expr,
Expression::BinaryOperation {
left: Box::new(Expression::Number(12.0)),
op: BinaryOperator::Eq,
right: Box::new(Expression::Number(12.0))
}
);
assert_eq!(evaluate(expr), Expression::Bool(true));
}
And the parser implementation looks like this (we add Eq and Ne to BinaryOperator):
pub Expression = Equality;
// equality : equality ( "!=" | "==" ) comparison
// | comparison
Equality = BinaryOperation<Equality, EqualityOp, Comparison, Comparison>;
EqualityOp: BinaryOperator = {
"==" => BinaryOperator::Eq,
"!=" => BinaryOperator::Ne,
};
The change to evaluate looks like this:
// ...
BinaryOperator::Eq => Expression::Bool(left == right),
BinaryOperator::Ne => Expression::Bool(left != right),