forked from shadowfacts/shadowfacts.net
Add Part 3: Basic Evaluation
This commit is contained in:
parent
8ba029c5a4
commit
9296bb1ddc
|
@ -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 = `<p style="font-style: italic;">This post is part of a <a href="/build-a-programming-language/" data-link="/build-a-programming-language/">series</a> about learning Rust and building a small programming language.</p><hr>`
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
<!-- excerpt-end -->
|
||||
|
||||
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.
|
Loading…
Reference in New Issue