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::prelude::*;
6
use uniplate::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()).copied().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
        && let Expr::Abs(x, g) = m.as_ref()
96
    {
97
        return Some(subst(g, *x, n));
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(Box::new(|m: &mut u32| *m += 1));
106
    }
107
    retval
108
}
109

            
110
fn reduce_all(expr: Expr) -> (Expr, u32) {
111
    let engine = EngineBuilder::new()
112
        .add_rule(transform_beta_reduce as RuleFn<_, _>)
113
        .build();
114
    engine.morph(expr, 0)
115
}
116

            
117
#[test]
118
fn simple_application() {
119
    // (\x. x) 1 -> 1
120
    let expr = Expr::App(
121
        Box::new(Expr::Abs(0, Box::new(Expr::Var(0)))),
122
        Box::new(Expr::Var(1)),
123
    );
124
    let (result, reductions) = reduce_all(expr);
125

            
126
    assert_eq!(result, Expr::Var(1));
127
    assert_eq!(reductions, 1);
128
}
129

            
130
#[test]
131
fn nested_application() {
132
    // ((\x. x) (\y. y)) 1 -> 1
133
    let expr = Expr::App(
134
        Box::new(Expr::App(
135
            Box::new(Expr::Abs(0, Box::new(Expr::Var(0)))),
136
            Box::new(Expr::Abs(1, Box::new(Expr::Var(1)))),
137
        )),
138
        Box::new(Expr::Var(2)),
139
    );
140
    let (result, reductions) = reduce_all(expr);
141

            
142
    assert_eq!(result, Expr::Var(2));
143
    assert_eq!(reductions, 2);
144
}
145

            
146
#[test]
147
fn capture_avoiding_substitution() {
148
    // (\x. (\y. x)) 1 -> (\y. 1)
149
    let expr = Expr::App(
150
        Box::new(Expr::Abs(0, Box::new(Expr::Abs(1, Box::new(Expr::Var(0)))))),
151
        Box::new(Expr::Var(1)),
152
    );
153
    let (result, reductions) = reduce_all(expr);
154

            
155
    assert_eq!(result, Expr::Abs(2, Box::new(Expr::Var(1))));
156
    assert_eq!(reductions, 1);
157
}
158

            
159
#[test]
160
fn double_reduction() {
161
    // (\x. (\y. y)) 1 -> (\y. y)
162
    let expr = Expr::App(
163
        Box::new(Expr::Abs(0, Box::new(Expr::Abs(1, Box::new(Expr::Var(1)))))),
164
        Box::new(Expr::Var(2)),
165
    );
166
    let (result, reductions) = reduce_all(expr);
167

            
168
    assert_eq!(result, Expr::Abs(1, Box::new(Expr::Var(1))));
169
    assert_eq!(reductions, 1);
170
}
171

            
172
#[test]
173
fn reduce_id() {
174
    // (\x. x) -> (\x. x)
175
    let expr = Expr::Abs(0, Box::new(Expr::Var(0)));
176
    let (result, reductions) = reduce_all(expr);
177

            
178
    assert_eq!(result, Expr::Abs(0, Box::new(Expr::Var(0))));
179
    assert_eq!(reductions, 0);
180
}
181

            
182
#[test]
183
fn no_reduction() {
184
    // x -> x
185
    let expr = Expr::Var(1);
186
    let (result, reductions) = reduce_all(expr);
187

            
188
    assert_eq!(result, Expr::Var(1));
189
    assert_eq!(reductions, 0);
190
}
191

            
192
#[test]
193
fn complex_expression() {
194
    // (((\x. (\y. x y)) (\z. z)) (\w. w)) -> (\y. (\w. w) y) -> (\w. w)
195
    let expr = Expr::App(
196
        Box::new(Expr::App(
197
            Box::new(Expr::Abs(
198
                0,
199
                Box::new(Expr::Abs(
200
                    1,
201
                    Box::new(Expr::App(Box::new(Expr::Var(0)), Box::new(Expr::Var(1)))),
202
                )),
203
            )),
204
            Box::new(Expr::Abs(2, Box::new(Expr::Var(2)))),
205
        )),
206
        Box::new(Expr::Abs(3, Box::new(Expr::Var(3)))),
207
    );
208
    let (result, reductions) = reduce_all(expr);
209

            
210
    assert_eq!(result, Expr::Abs(3, Box::new(Expr::Var(3))));
211
    assert_eq!(reductions, 3);
212
}