Alıştırma: İfade Değerlendirmesi

Aritmetik ifadeler için basit bir özyinelemeli değerlendirici (recursive evaluator) yazalım.

Küçük bir aritmetik ifadenin bir örneği, 30 olarak değerlendirilen 10 + 20 olabilir. İfadeyi bir ağaç olarak temsil edebiliriz:

+1020

Daha büyük ve daha karmaşık bir ifade, 85 olarak değerlendirilen (10 * 9) + ((3 - 4) * 5) olacaktır. Bunu çok daha büyük bir ağaç olarak temsil ediyoruz:

+**109-534

Kodda, ağacı iki türle temsil (represent) edeceğiz:

/// İki alt ifade (subexpression) üzerinde gerçekleştirilecek bir işlem.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// Ağaç şeklinde bir ifade (expression).
#[derive(Debug)]
enum Expression {
    /// İki alt ifade üzerinde bir işlem.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// Bir değişmez (literal) değer
    Value(i64),
}

Box türü burada bir akıllı göstericidir (smart pointer) ve kursun ilerleyen bölümlerinde ayrıntılı olarak ele alınacaktır. Bir ifade, testlerde görüldüğü gibi Box::new ile “kutulanabilir (boxed)”. Kutulanmış bir ifadeyi değerlendirmek için, onu “kutudan çıkarmak (unbox)” için referans kaldırma (deref) operatörünü (*) kullanın: eval(*boxed_expr).

Kodu Rust deneme alanına (playground) kopyalayıp yapıştırın ve eval’i uygulamaya başlayın. Son ürün testleri geçmelidir. todo!() kullanmak ve testleri birer birer geçmek yardımcı olabilir. Bir testi geçici olarak #[ignore] ile de atlayabilirsiniz:

#[test]
#[ignore]
fn test_value() { .. }
/// İki alt ifade (subexpression) üzerinde gerçekleştirilecek bir işlem.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// Ağaç şeklinde bir ifade (expression).
#[derive(Debug)]
enum Expression {
    /// İki alt ifade üzerinde bir işlem.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// Bir değişmez (literal) değer
    Value(i64),
}

fn eval(e: Expression) -> i64 {
    todo!()
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), 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)),
        }),
        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),
        }),
        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))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
}

#[test]
fn test_div() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Div,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(2)),
        }),
        5
    )
}