nidomiro

Software developer stories
en de

Rust-04: More operators and loops

In the last post we improved the simple program to be extendable with other operators. In this post we will implement more operators and a loop.

Let’s start with the simple task: adding more operators. To support more operators we have to change two parts of the calculator, the parsing and the evaluation. First let’s look into changing the parser. For this we simply add the new operators (-*/) to the regex inside Operation::from_string. The resulting regex will look like this: ^\s*(\d+)\s*([+\-*/])\s*(\d+)\s*$. Now the parser accepts the new operators; first part done.

The second part to change is the evaluation of the parsed operation. Adding the new operators is as simple as adding more arms to match:

let result = match operation {
    Operation { left, operator: '+', right } => Ok(left + right),
    Operation { left, operator: '-', right } => Ok(left - right),
    Operation { left, operator: '*', right } => Ok(left * right),
    Operation { left, operator: '/', right } => Ok(left / right),
    x => Err(format!("{} is not a valid operator", x.operator)),
};

With the current state, the app can now accept +, -, *, / as operators and calculate the result.

Since we expanded the capabilities, we can also look for some possible refactorings. You might see, that the execution of the Operation still happens inside the main function. It would be cleaner to move the evaluation closer to Operation itself. So let’s add another function to impl Operation called execute and move the mentioned code to that function.

impl Operation {
    // <snip>
    
    fn execute(&self) -> Result<i32, String> {
        match self {
            Operation { left, operator: '+', right } => Ok(left + right),
            Operation { left, operator: '-', right } => Ok(left - right),
            Operation { left, operator: '*', right } => Ok(left * right),
            Operation { left, operator: '/', right } => Ok(left / right),
            x => Err(format!("{} is not a valid operator", x.operator)),
        }
    }
}

Right now execute only returns a String in case of an error. To be more specific, what error occurred we add another struct to represent this error.

struct InvalidOperatorError { operator: char }

// <snip>

impl Operation {
    // <snip>

    fn execute(&self) -> Result<i32, InvalidOperatorError> {
        match self {
            Operation { left, operator: '+', right } => Ok(left + right),
            Operation { left, operator: '-', right } => Ok(left - right),
            Operation { left, operator: '*', right } => Ok(left * right),
            Operation { left, operator: '/', right } => Ok(left / right),
            x => Err(InvalidOperatorError {operator: x.operator}),
        }
    }
}

After these changes, we are at state: 2d6c605a.

Until now, we have completed task one: implementing more possible operators. The second task is to change the calculator to accept multiple calculations one after the other. A loop is a prime example to achieve this behaviour.

Just wrapping everything in a loop does enable multiple calculations, but the user of our calculator has no way of exiting the program. We want to be able to type ‘quit’ to exit the program. The code below will achieve just that.

fn main() {
    loop {
        println!("Please enter a calculation (or quit to exit)");
    
        let mut input = String::new();
        std::io::stdin()
            .read_line(&mut input)
            .expect("Could not read line");
        
        if input.trim().eq("quit") {
            println!("exiting...");
            break
        }
    
        let operation = Operation::from_string(&input).expect("Your input is invalid");
    
        let result = operation.execute();
    
    
        println!("The result is: {}", result.unwrap());
    }
}

But we have another problem to fix.

Since we allow multiple calculations we don’t want the calculator to crash on an invalid input anymore. Using expect and unwrap as error-handlers, will result in a crash of the app if they encounter an error-state; so we need to exchange them. Obviously we use match instead:

fn main() {
    loop {
        println!("Please enter a calculation (or quit to exit)");

        let mut input = String::new();
        std::io::stdin()
            .read_line(&mut input)
            .expect("Could not read line");

        if input.trim().eq("quit") {
            println!("exiting...");
            break
        }

        let operation = match Operation::from_string(&input) {
            None => {
                println!("Your input was invalid");
                continue
            },
            Some(x) => x
        };

        let result = match operation.execute() {
            Err(InvalidOperatorError { operator: op}) => {
                println!("The operator {} is invalid", op);
                continue
            },
            Ok(x) => x
        };


        println!("The result is: {}", result);
    }
}

Now we get an info, if the input is invalid and can try again on the next iteration.

After all changes of this post, we are at state: 8322be09.

In the next post we will change the structure radically to be able to support multiple operators in one input - at least on the execution side.


This post is part of a series: