In this post, I briefly describe the implementation of the code in Chapter 6: Parsing Expressions, and Chapter 7: Evaluating Expressions.

🦀 Index of the Complete Series.

143-feature-image.png
rlox: A Rust Implementation of “Crafting Interpreters” – Parsing and Evaluating Expressions

🚀 Please note, code for this post can be downloaded from GitHub with:

git clone -b v0.2.0 https://github.com/behai-nguyen/rlox.git

Running the CLI Application

To run the CLI application, use the following command:

$ cargo run --release

Enter a valid expression such as ((4.5 / 2) * 2) == 4.50. This should produce the following output:

Expression: (== (group (* (group (/ 4.5 2.0)) 2.0)) 4.5)
Evaluated to: true

🚀 At this point, the CLI application is capable as a basic arithmetic calculator which supports addition, subtraction, multiplication and division.

Please also refer to this section of the README.md for more information.

Updated Repository Layout

Legend: = updated, = new.

💥 Unmodified files are omitted for brevity.

.
├── docs
│   └── RLoxGuide.md ☆
├── README.md
├── src
│   ├── ast_printer.rs ★
│   ├── expr.rs ★
│   ├── interpreter.rs ☆
│   ├── lib.rs ★
│   ├── main.rs ★
│   ├── parser.rs ☆
│   └── stmt.rs ★
├── tests
│   ├── data
│   │   └── expressions ☆
│   │       ├── evaluate.lox
│   │       ├── parse-02.lox
│   │       ├── parse-03.lox
│   │       ├── parse.lox
│   │       └── README.md
│   ├── test_common.rs ★
│   ├── test_interpreter.rs ☆
│   └── test_parser.rs ☆
└── tool
    └── generate_ast
        └── src
            └── main.rs ★

As the src/interpreter.rs module was being developed, I have found that it would be more efficient to propagate the error up until the main() function. This is not unexpected, as stated toward the end of this section in a previous post. Let’s cover those first.

Expressions and Statements Refactoring

In both modules expr.rs; and stmt.rs, all Visitor<T> trait’s visit_()* and enum’s accept() methods now return Result<T, LoxError>. This led to the following changes:

  1. As discussed, both expr.rs and stmt.rs are generated by a standalone tool. The relevant parts of this tool have been updated to produce the desired output. Reference.
  2. Most of the methods in the src/ast_printer.rs module had to be refactored in response to the above changes.

Parsing Expressions

This is Chapter 6. I have completed the parsing code in this chapter only:

  1. src/parser.rs: The parser code.
  2. tests/test_parser.rs: Tests for the parser module.

The parser has been wired in the src/main.rs module, it takes in a list of tokens, which is produced by the scanner. If parsing succeeds, we first call src/ast_printer.rs to display the resulting expression. Then passing the expression to the src/interpreter.rs module to evaluate.

Evaluating Expressions

And this is Chapter 7. The following expressions are implemented (evaluated): Expr::Binary, Expr::Grouping, Expr::Literal and Expr::Unary.

🦀 Please recall that the Lox language supports only the following data types:

  1. Number: f64 in Rust. E.g. 1.05.
  2. String: String in Rust. E.g. abcd.
  3. Boolean: bool in Rust. E.g. true or false.
  4. Nil: Expressed as nil without quotations.

Expressions are evaluated to values of one of the above data types.

And I have also completed only the code for this chapter:

  1. src/interpreter.rs: The interpreter code.
  2. tests/test_interpreter.rs: Exhaustive tests for the interpreter module.

For the Expr::Grouping enum, the corresponding visit_grouping_expr() method doesn’t compute anything itself, it traverses the AST to perform recursive evaluation of grouped sub-expressions: I have not written any test for this expression.

With regard to the Expr::Literal enum, whose corresponding visit_literal_expr() method, does not have to implement any operator, it just returns the underlying literal value. Whereas:

  1. Expr::Binary implements the following operator >, >=, <, <=, !=, ==, +, -, *, and / via method visit_binary_expr().
  2. Expr::Unary implements ! and - via method visit_unary_expr().

Please refer to the RLox Language Guide’s Expressions and Operators for more detail.

The interpreter test module includes exhaustive tests for all Expr::Literal data types, all Expr::Binary and Expr::Unary operators.

What’s Next

There is still a long journey ahead… I’m studying each chapter in turn, implementing the code and documenting my progress as I go.

There are still warnings about dead code—these, I’m happy to ignore them for now.

Thank you for reading! I hope this post helps others following the same journey. As always—stay curious, stay safe 🦊

✿✿✿

Feature image sources:

🦀 Index of the Complete Series.