1
//! Here we implement a simple lambda calculus interpreter.
2
//! The beta-reduction rule has the side effect of increasing a counter in the metadata.
3

            
4
use std::collections::HashSet;
5
use tree_morph::*;
6
use uniplate::derive::Uniplate;
7

            
8
#[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
9
#[uniplate()]
10
enum Expr {
11
    Abs(u32, Box<Expr>),
12
    App(Box<Expr>, Box<Expr>),
13
    Var(u32),
14
}
15

            
16
impl Expr {
17
    fn free_vars(&self) -> HashSet<u32> {
18
        match self {
19
            Expr::Var(x) => {
20
                let mut set = HashSet::new();
21
                set.insert(*x);
22
                set
23
            }
24
            Expr::App(m, n) => {
25
                let mut set = m.free_vars();
26
                set.extend(n.free_vars());
27
                set
28
            }
29
            Expr::Abs(x, m) => {
30
                let mut set = m.free_vars();
31
                set.remove(x);
32
                set
33
            }
34
        }
35
    }
36

            
37
    fn vars(&self) -> HashSet<u32> {
38
        match self {
39
            Expr::Var(x) => {
40
                let mut set = HashSet::new();
41
                set.insert(*x);
42
                set
43
            }
44
            Expr::App(m, n) => {
45
                let mut set = m.vars();
46
                set.extend(n.vars());
47
                set
48
            }
49
            Expr::Abs(x, m) => {
50
                let mut set = m.vars();
51
                set.insert(*x);
52
                set
53
            }
54
        }
55
    }
56
}
57

            
58
// Create a fresh unused variable
59
fn fresh(vars: &HashSet<u32>) -> u32 {
60
    *vars.iter().max().unwrap_or(&0) + 1
61
}
62

            
63
// Capture-avoiding substitution
64
fn subst(expr: &Expr, x: u32, f: &Expr) -> Expr {
65
    if !expr.free_vars().contains(&x) {
66
        return expr.clone(); // x is bound or unused
67
    }
68

            
69
    match expr {
70
        Expr::Var(y) => {
71
            if *y == x {
72
                f.clone()
73
            } else {
74
                Expr::Var(*y)
75
            }
76
        }
77
        Expr::App(m, n) => Expr::App(Box::new(subst(m, x, f)), Box::new(subst(n, x, f))),
78
        Expr::Abs(y, g) => {
79
            if *y == x {
80
                Expr::Abs(*y, g.clone()) // binding occurrence
81
            } else if !f.free_vars().contains(y) {
82
                Expr::Abs(*y, Box::new(subst(g, x, f))) // substitution is safe
83
            } else {
84
                let z = fresh(&f.vars().union(&f.vars()).cloned().collect());
85
                let new_g = subst(g, *y, &Expr::Var(z));
86
                Expr::Abs(z, Box::new(subst(&new_g, x, f))) // capture avoidance
87
            }
88
        }
89
    }
90
}
91

            
92
// Beta-reduction
93
fn beta_reduce(expr: &Expr) -> Option<Expr> {
94
    if let Expr::App(m, n) = expr {
95
        if let Expr::Abs(x, g) = m.as_ref() {
96
            return Some(subst(&g, *x, n));
97
        }
98
    }
99
    None
100
}
101

            
102
fn transform_beta_reduce(cmd: &mut Commands<Expr, u32>, expr: &Expr, _: &u32) -> Option<Expr> {
103
    let retval = beta_reduce(expr);
104
    if retval.is_some() {
105
        cmd.mut_meta(|m| *m += 1);
106
    }
107
    retval
108
}
109

            
110
#[test]
111
fn test_simple_application() {
112
    // (\x. x) 1 -> 1
113
    let expr = Expr::App(
114
        Box::new(Expr::Abs(0, Box::new(Expr::Var(0)))),
115
        Box::new(Expr::Var(1)),
116
    );
117
    let (result, meta) = reduce(transform_beta_reduce, expr, 0);
118
    assert_eq!(result, Expr::Var(1));
119
    assert_eq!(meta, 1);
120
}
121

            
122
#[test]
123
fn test_nested_application() {
124
    // ((\x. x) (\y. y)) 1 -> 1
125
    let expr = Expr::App(
126
        Box::new(Expr::App(
127
            Box::new(Expr::Abs(0, Box::new(Expr::Var(0)))),
128
            Box::new(Expr::Abs(1, Box::new(Expr::Var(1)))),
129
        )),
130
        Box::new(Expr::Var(2)),
131
    );
132
    let (result, meta) = reduce(transform_beta_reduce, expr, 0);
133
    assert_eq!(result, Expr::Var(2));
134
    assert_eq!(meta, 2);
135
}
136

            
137
#[test]
138
fn test_capture_avoiding_substitution() {
139
    // (\x. (\y. x)) 1 -> (\y. 1)
140
    let expr = Expr::App(
141
        Box::new(Expr::Abs(0, Box::new(Expr::Abs(1, Box::new(Expr::Var(0)))))),
142
        Box::new(Expr::Var(1)),
143
    );
144
    let (result, meta) = reduce(transform_beta_reduce, expr, 0);
145
    assert_eq!(result, Expr::Abs(2, Box::new(Expr::Var(1))));
146
    assert_eq!(meta, 1);
147
}
148

            
149
#[test]
150
fn test_double_reduction() {
151
    // (\x. (\y. y)) 1 -> (\y. y)
152
    let expr = Expr::App(
153
        Box::new(Expr::Abs(0, Box::new(Expr::Abs(1, Box::new(Expr::Var(1)))))),
154
        Box::new(Expr::Var(2)),
155
    );
156
    let (result, meta) = reduce(transform_beta_reduce, expr, 0);
157
    assert_eq!(result, Expr::Abs(1, Box::new(Expr::Var(1))));
158
    assert_eq!(meta, 1);
159
}
160

            
161
#[test]
162
fn test_id() {
163
    // (\x. x) -> (\x. x)
164
    let expr = Expr::Abs(0, Box::new(Expr::Var(0)));
165
    let (expr, meta) = reduce(transform_beta_reduce, expr, 0);
166
    assert_eq!(expr, Expr::Abs(0, Box::new(Expr::Var(0))));
167
    assert_eq!(meta, 0);
168
}
169

            
170
#[test]
171
fn test_no_reduction() {
172
    // x -> x
173
    let expr = Expr::Var(1);
174
    let (result, meta) = reduce(transform_beta_reduce, expr.clone(), 0);
175
    assert_eq!(result, expr);
176
    assert_eq!(meta, 0);
177
}
178

            
179
#[test]
180
fn test_complex_expression() {
181
    // (((\x. (\y. x y)) (\z. z)) (\w. w)) -> (\y. (\w. w) y) -> (\w. w)
182
    let expr = Expr::App(
183
        Box::new(Expr::App(
184
            Box::new(Expr::Abs(
185
                0,
186
                Box::new(Expr::Abs(
187
                    1,
188
                    Box::new(Expr::App(Box::new(Expr::Var(0)), Box::new(Expr::Var(1)))),
189
                )),
190
            )),
191
            Box::new(Expr::Abs(2, Box::new(Expr::Var(2)))),
192
        )),
193
        Box::new(Expr::Abs(3, Box::new(Expr::Var(3)))),
194
    );
195
    let (result, meta) = reduce(transform_beta_reduce, expr, 0);
196
    assert_eq!(result, Expr::Abs(3, Box::new(Expr::Var(3))));
197
    assert_eq!(meta, 3);
198
}