1
// Example case from the 05/02/2025 Conjure VIP meeting
2

            
3
use tree_morph::{helpers::select_panic, prelude::*};
4
use uniplate::Uniplate;
5

            
6
#[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
7
#[uniplate()]
8
enum Shape {
9
    Circle(Box<Shape>),
10
    Square,
11
    Triangle,
12
}
13

            
14
// O(/\) ~> /\
15
fn circ_tri_to_tri(_: &mut Commands<Shape, ()>, expr: &Shape, _: &()) -> Option<Shape> {
16
    if let Shape::Circle(inner) = expr
17
        && let Shape::Triangle = **inner
18
    {
19
        return Some(Shape::Triangle);
20
    }
21
    None
22
}
23

            
24
// O(O(/\)) ~> []
25
fn circ_circ_tri_to_sqr(_: &mut Commands<Shape, ()>, expr: &Shape, _: &()) -> Option<Shape> {
26
    if let Shape::Circle(inner) = expr
27
        && let Shape::Circle(inner_inner) = inner.as_ref()
28
        && let Shape::Triangle = **inner_inner
29
    {
30
        return Some(Shape::Square);
31
    }
32
    None
33
}
34

            
35
#[test]
36
fn circ_tri() {
37
    // O(/\)
38
    let expr = Shape::Circle(Box::new(Shape::Triangle));
39

            
40
    let engine = EngineBuilder::new()
41
        .add_rule(circ_tri_to_tri as RuleFn<_, _>)
42
        .add_rule(circ_circ_tri_to_sqr as RuleFn<_, _>)
43
        .build();
44
    let (result, _) = engine.morph(expr, ());
45

            
46
    assert_eq!(result, Shape::Triangle);
47
}
48

            
49
#[test]
50
fn circ_circ_tri() {
51
    // O(O(/\))
52
    let expr = Shape::Circle(Box::new(Shape::Circle(Box::new(Shape::Triangle))));
53

            
54
    // Same priority group - 2nd rule applies first as it applies higher in the tree
55
    let engine = EngineBuilder::new()
56
        .add_rule_group(rule_fns![circ_tri_to_tri, circ_circ_tri_to_sqr])
57
        .build();
58
    let (result, _) = engine.morph(expr, ());
59

            
60
    assert_eq!(result, Shape::Square);
61
}
62

            
63
#[test]
64
fn shape_higher_priority() {
65
    // O(O(/\))
66
    let expr = Shape::Circle(Box::new(Shape::Circle(Box::new(Shape::Triangle))));
67

            
68
    // Higher priority group - 1st rule applies first even though it applies lower in the tree
69
    let engine = EngineBuilder::new()
70
        .add_rule(circ_tri_to_tri as RuleFn<_, _>)
71
        .add_rule(circ_circ_tri_to_sqr as RuleFn<_, _>)
72
        .build();
73
    let (result, _) = engine.morph(expr, ());
74

            
75
    // O(O(/\)) ~> O(/\) ~> /\
76
    assert_eq!(result, Shape::Triangle);
77
}
78

            
79
#[should_panic]
80
#[test]
81
fn shape_multiple_rules_panic() {
82
    // O(O(/\))
83
    let expr = Shape::Circle(Box::new(Shape::Circle(Box::new(Shape::Triangle))));
84

            
85
    // Same rule twice, applicable at the same time
86
    let engine = EngineBuilder::new()
87
        .set_selector(select_panic)
88
        .add_rule_group(rule_fns![circ_tri_to_tri, circ_tri_to_tri])
89
        .build();
90
    engine.morph(expr, ());
91
}