演習: 式の評価

演算式用の簡単な再帰エバリュエータを作成してみましょう。

An example of a small arithmetic expression could be 10 + 20, which evaluates to 30. We can represent the expression as a tree:

+1020

A bigger and more complex expression would be (10 * 9) + ((3 - 4) * 5), which evaluate to 85. We represent this as a much bigger tree:

+**109-534

In code, we will represent the tree with two types:

#![allow(unused)]
fn main() {
/// 2 つのサブ式に対して実行する演算。
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// ツリー形式の式。
#[derive(Debug)]
enum Expression {
    /// 2 つのサブ式に対する演算。
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// リテラル値
    Value(i64),
}
}

ここでの Box 型はスマート ポインタです。詳細はこの講座で後ほど説明します。テストで見られるように、式は Box::new で「ボックス化」できます。ボックス化された式を評価するには、逆参照演算子(*)を使用して「ボックス化解除」します(eval(*boxed_expr))。

一部の式は評価できず、エラーが返されます。標準の Result<Value, String> 型は、成功した値(Ok(Value))またはエラー(Err(String))のいずれかを表す列挙型です。この型については、後ほど詳しく説明します。

コードをコピーして Rust プレイグラウンドに貼り付け、eval の実装を開始します。完成したエバリュエータはテストに合格する必要があります。todo!() を使用して、テストを 1 つずつ実施することをおすすめします。#[ignore] を使用して、テストを一時的にスキップすることもできます。

#[test]
#[ignore]
fn test_value() { .. }
#![allow(unused)]
fn main() {
/// 2 つのサブ式に対して実行する演算。
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// ツリー形式の式。
#[derive(Debug)]
enum Expression {
    /// 2 つのサブ式に対する演算。
    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_zeros() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
}

#[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("division by zero"))
    );
}
}