1
#![allow(dead_code)]
2
use conjure_cp::ast::eval::vec_op;
3
use conjure_cp::ast::{
4
    Atom, Expression as Expr, Metadata, SymbolTable, eval_constant, run_partial_evaluator,
5
};
6
use conjure_cp::rule_engine::{
7
    ApplicationError::RuleNotApplicable, ApplicationResult, Reduction, register_rule,
8
    register_rule_set,
9
};
10
use std::sync::Arc;
11
use std::sync::atomic::{AtomicBool, Ordering};
12
use uniplate::Biplate;
13

            
14
register_rule_set!("Constant", ());
15

            
16
#[register_rule(("Base",9000))]
17
1389996
fn partial_evaluator(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
18
1389996
    run_partial_evaluator(expr)
19
1389996
}
20

            
21
#[register_rule(("Constant", 9001))]
22
1404924
fn constant_evaluator(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
23
    // I break the rules a bit here: this is a global rule!
24
    //
25
    // This rule is really really hot when expanding comprehensions.. Also, at time of writing, we
26
    // have the naive rewriter, which is slow on large trees....
27
    //
28
    // Also, constant_evaluating bottom up vs top down does things in less passes: the rewriter,
29
    // however, favour doing this top-down!
30
    //
31
    // e.g. or([(1=1),(2=2),(3+3 = 6)])
32

            
33
1404924
    if !matches!(expr, Expr::Root(_, _)) {
34
1346487
        return Err(RuleNotApplicable);
35
58437
    };
36

            
37
58437
    let has_changed: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
38
58437
    let has_changed_2 = Arc::clone(&has_changed);
39

            
40
2178720
    let new_expr = expr.transform_bi(&move |x| {
41
1114362
        if let Expr::Atomic(_, Atom::Literal(_)) = x {
42
432315
            return x;
43
1746405
        }
44

            
45
1746405
        match eval_constant(&x)
46
1746405
            .map(|c| Expr::Atomic(Metadata::new(), Atom::Literal(c)))
47
1746405
            .or_else(|| run_partial_evaluator(&x).ok().map(|r| r.new_expression))
48
        {
49
33810
            Some(new_expr) => {
50
33810
                has_changed.store(true, Ordering::Relaxed);
51
33810
                new_expr
52
            }
53

            
54
1712595
            None => x,
55
        }
56
2178720
    });
57

            
58
58437
    if has_changed_2.load(Ordering::Relaxed) {
59
13845
        Ok(Reduction::pure(new_expr))
60
    } else {
61
44592
        Err(RuleNotApplicable)
62
    }
63
1404924
}
64

            
65
/// Evaluate the root expression.
66
///
67
/// This returns either Expr::Root([true]) or Expr::Root([false]).
68
#[register_rule(("Constant", 9001))]
69
1404924
fn eval_root(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
70
    // this is its own rule not part of apply_eval_constant, because root should return a new root
71
    // with a literal inside it, not just a literal
72

            
73
1404924
    let Expr::Root(_, exprs) = expr else {
74
1346487
        return Err(RuleNotApplicable);
75
    };
76

            
77
58437
    match exprs.len() {
78
462
        0 => Ok(Reduction::pure(Expr::Root(
79
462
            Metadata::new(),
80
462
            vec![true.into()],
81
462
        ))),
82
19257
        1 => Err(RuleNotApplicable),
83
        _ => {
84
18
            let lit =
85
38718
                vec_op::<bool, bool>(|e| e.iter().all(|&e| e), exprs).ok_or(RuleNotApplicable)?;
86

            
87
18
            Ok(Reduction::pure(Expr::Root(
88
18
                Metadata::new(),
89
18
                vec![lit.into()],
90
18
            )))
91
        }
92
    }
93
1404924
}
94

            
95
#[cfg(test)]
96
mod tests {
97
    use conjure_cp::ast::{Expression, Moo, eval_constant};
98
    use conjure_cp::essence_expr;
99

            
100
    #[test]
101
3
    fn div_by_zero() {
102
3
        let expr = essence_expr!(1 / 0);
103
3
        assert_eq!(eval_constant(&expr), None);
104
3
    }
105

            
106
    #[test]
107
3
    fn safediv_by_zero() {
108
3
        let expr = Expression::SafeDiv(
109
3
            Default::default(),
110
3
            Moo::new(essence_expr!(1)),
111
3
            Moo::new(essence_expr!(0)),
112
3
        );
113
3
        assert_eq!(eval_constant(&expr), None);
114
3
    }
115
}