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

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

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

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

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

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

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

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

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

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

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