Вправа: обчислення виразу

Давайте напишемо простий рекурсивний обчислювач арифметичних виразів.

Тип Box тут є розумним вказівником і буде детально розглянутий пізніше у курсі. Вираз може бути "упаковано" за допомогою Box::new, як показано у тестах. Щоб обчислити вираз, використайте оператор розіменування (*), щоб "розпакувати" його: eval(*boxed_expr).

Деякі вирази не можуть бути обчислені і повертають помилку. Стандартний тип Result<Value, String> - це перелік, який представляє або успішне значення (Ok(Value)), або помилку (Err(String)). Ми розглянемо цей тип більш детально пізніше.

Скопіюйте та вставте код у середовище Rust і почніть реалізацію eval. Кінцевий продукт повинен пройти тести. Може бути корисно використати todo!() і змусити тести проходити один за одним. Ви також можете тимчасово оминути тест за допомогою #[ignore]:

#[test]
#[ignore]
fn test_value() { .. }

Якщо ви закінчили раніше, спробуйте написати тест, який призводить до ділення на нуль або цілочисельного переповнення. Як ви могли б впоратися з цим за допомогою Result замість того, щоб панікувати?

#![allow(unused)]
fn main() {
/// Операція для виконання над двома під-виразами.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// Вираз у вигляді дерева.
#[derive(Debug)]
enum Expression {
    /// Операція над двома підвиразами.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// Літеральне значення
    Value(i64),
}

fn eval(e: Expression) -> Result<i64, String> {
    todo!()
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), Ok(19));
}

#[test]
fn test_sum() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(20)),
        }),
        Ok(30)
    );
}

#[test]
fn test_recursion() {
    let term1 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Value(10)),
        right: Box::new(Expression::Value(9)),
    };
    let term2 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(3)),
            right: Box::new(Expression::Value(4)),
        }),
        right: Box::new(Expression::Value(5)),
    };
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(term1),
            right: Box::new(term2),
        }),
        Ok(85)
    );
}

#[test]
fn test_error() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Div,
            left: Box::new(Expression::Value(99)),
            right: Box::new(Expression::Value(0)),
        }),
        Err(String::from("ділення на нуль"))
    );
}
}