High-Tech Institute // Exploring Rust // Part
x (extra exercises)
Kris van Rens
In this exercise, you’ll implement a simple calculator using Reverse Polish Notation (RPN).
You will likely be most familiar to algebraic notation, or infix notation, so first read about RPN first to get an idea of what it is and why it’s easy to implement an interpreter for it. It will take a bit of getting used to RPN before really appreciating it.
\(5 + 2\)
\(7 * 21 - 9\)
\((220 / 7 + 10) * 4\)
\(5 \space 2 \space +\)
\(7 \space 21 \space * \space 9 \space -\)
\(220 \space 7 \space / \space 1 \space + \space 4 \space *\)
Each operand is pushed onto the stack, each operator will pop the operands it needs off the stack and pushes its result back onto the stack. It never needs more than two memory cells.
For the sake of keeping the end result under ±250 lines of code, we’ll keep it simple:
+
/ -
/ *
/ /
/
%
There’s a suite of example tests in the solution code that can be used to validate your program.
Feel free to extend your version at will, my solution code is just one of many possibilities.
As a first basic step, implement a program to test your build setup.
It doesn’t have to be functional otherwise.
Create a new binary executable project using cargo
.
Add the code to read a string of user input from the command-line terminal.
In the best case, your program can accept any data from “standard input”.
This can be used for testing:
Shell echo |
echo '5 2 *' | cargo run |
Bash “here strings” | cargo run <<< '5 2 *' |
Add the code to categorize the input string. Classify the code input data as either:
The next step in input classification is tokenization: the process of wrapping the classified data into distinct Rust types. With this step we basically “plug in” to the Rust type system and benefit from it.
When modeling the data type(s) for a token, consider the number of states it can have and how many are valid. Try to reach an optimal mapping by minimizing the amount of possible invalid states.
Also add the code to parse the string data from the token into value that can be used in calculations.
In this step, we’ll move the code out of fn main
, and
create a separate function for it.
Carefully design the function API, and consider argument/return types, error handling and tests.
Hint: The solution code uses an
impl Trait
argument type to make the function as generic as
possible. This not only improves reuse possibilities, but also
facilitates testing. Read about it here
and here.
Add the code to perform a single calculation using the parsed and tokenized user input.
Add the code for proper stack-based memory to store operands and results.
In order to support longer calculations, like \(7 \space 21 \space * \space 9 \space -\), we must implement a state machine.
The result of each calculation is stored on the stack, as if it were the first operand. We also need a way to detect the calculation end, which will influence the behavior of the calculator.
Again, make sure errors are handled properly. Input/calculation errors are not fatal errors (i.e. not panics), and must be handled separately.
Find situations where calculations fail, and add code to remediate / gracefully fail.
For example, what happens if we enter:
Let’s reorganize our code into library components
(types/methods/functions), and an executable (fn main
).
Keep the function unit tests local to the functions, or move them to
the /tests
directory.
Also add documentation for all library components. Make sure your
documentation features example code, that is tested when running
cargo test
.
In the RPN calculator, the state machine simply keeps on reading from
the token source, until the end of the token stream is reached. This
“sequence source”-like behavior sounds very much like how we would work
with iterators. Would it be possible to build an iterator type around
the read_token
function? Let’s find out!
Read about iterators here and here. Be sure to deal with errors correctly; writing the iterator itself is easy, error handling makes it more tricky. Good luck!
High-Tech Institute // Exploring Rust // Part x (extra exercises)