diff --git a/site/posts/2021-04-15-evaluation.md b/site/posts/2021-04-15-evaluation.md new file mode 100644 index 0000000..8a726a7 --- /dev/null +++ b/site/posts/2021-04-15-evaluation.md @@ -0,0 +1,76 @@ +``` +metadata.title = "Part 3: Basic Evaluation" +metadata.tags = ["build a programming language", "rust"] +metadata.date = "2021-04-15 17:00:42 -0400" +metadata.shortDesc = "A bad calculator." +metadata.slug = "evaluation" +metadata.preamble = `

This post is part of a series about learning Rust and building a small programming language.


` +``` + +Last time I said operator precedence was going to be next. Well, if you've read the title, you know that's not the case. I decided I really wanted to see this actually run^[evaluate] some code^[a single expression], so let's do that. + + + +First, there needs to be something to actually store values during the evaluation process. For this, I used yet another enum. It only has one case for now because we can currently only lex and parse integer values and one arithmetic operator. + +```rust +enum Value { + Integer(i64), +} +``` + +There's also a helper function to extract the underlying integer from a value in places where we're certain it's going to be an integer: + +```rust +impl Value { + fn expect_integer(&self, &msg) -> i64 { + match self { + Value::Integer(n) => *n, + _ => panic!("{}", msg), + } + } +} +``` + +The compiler warns about the unreachable match arm, but it'll be useful once there are more types of values. (Once again, actual error reporting will wait.) + +The actual evaulation starts in the `eval` function which takes a reference to the node to evaluate and returns a `Value` representing its result. + +For integer nodes, the value of the AST node is wrapped in a Value and returned directly. For binary operator (i.e. addition) nodes the left- and right-hand values are extracted and another function is called to perform the operation. + +```rust +fn eval(node: &Node) -> Value { + match node { + Node::Integer(n) => Value::Integer(*n), + Node::BinaryOp { left, right } => eval_binary_op(left, right), + } +} +``` + +This `eval_binary_op` function takes each of the nodes and calls `eval` with it. By doing this, it recurses through the the AST evaluating each node in a depth-first manner. It then turns each value into an integer (panicking if either isn't what it expects) and returns a new Value with the values added together. + +```rust +fn eval_binary_op(left: &Node, right: &Node) -> Value { + let left = eval(left).expect_integer("left hand side of binary operator must be an integer"); + let right = eval(right).expect_integer("right hand side of binary operator must be an integer"); + Value::Integer(left + right) +} +``` + +And with that surpisingly small amount of code, I've got a very dumb calculator that can perform arbitrary additions: + +```rust +fn main() { + let tokens = tokenize("1 + 2 + 3"); + if let Some(node) = parse(tokens) { + println!("result: {:?}", eval(&node)); + } +} +``` + +```sh +$ cargo run +result: Integer(6) +``` + +Next time, I'll add some more operators and actually get around to operator precedence.