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
131240
fn partial_evaluator(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
18
131240
    run_partial_evaluator(expr)
19
131240
}
20

            
21
#[register_rule(("Constant", 9001))]
22
133044
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
133044
    if !matches!(expr, Expr::Root(_, _)) {
34
125296
        return Err(RuleNotApplicable);
35
7748
    };
36

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

            
40
205902
    let new_expr = expr.transform_bi(&move |x| {
41
97942
        if let Expr::Atomic(_, Atom::Literal(_)) = x {
42
46450
            return x;
43
159452
        }
44

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

            
54
154463
            None => x,
55
        }
56
205902
    });
57

            
58
7748
    if has_changed_2.load(Ordering::Relaxed) {
59
1746
        Ok(Reduction::pure(new_expr))
60
    } else {
61
6002
        Err(RuleNotApplicable)
62
    }
63
133044
}
64

            
65
/// Evaluate the root expression.
66
///
67
/// This returns either Expr::Root([true]) or Expr::Root([false]).
68
#[register_rule(("Constant", 9001))]
69
133044
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
133044
    let Expr::Root(_, exprs) = expr else {
74
125296
        return Err(RuleNotApplicable);
75
    };
76

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

            
87
6
            Ok(Reduction::pure(Expr::Root(
88
6
                Metadata::new(),
89
6
                vec![lit.into()],
90
6
            )))
91
        }
92
    }
93
133044
}
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
1
    fn div_by_zero() {
102
1
        let expr = essence_expr!(1 / 0);
103
1
        assert_eq!(eval_constant(&expr), None);
104
1
    }
105

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