1
use tracing::instrument;
2
use uniplate::{Biplate, Uniplate};
3

            
4
use crate::{
5
    ast::{Atom, DecisionVariable, Domain, Expression as Expr, Name, ReturnType},
6
    bug,
7
    metadata::Metadata,
8
    Model,
9
};
10

            
11
/// True iff `expr` is an `Atom`.
12
16524
pub fn is_atom(expr: &Expr) -> bool {
13
16524
    matches!(expr, Expr::Atomic(_, _))
14
16524
}
15

            
16
/// True if `expr` is flat; i.e. it only contains atoms.
17
986
pub fn is_flat(expr: &Expr) -> bool {
18
1292
    for e in expr.children() {
19
1292
        if !is_atom(&e) {
20
884
            return false;
21
408
        }
22
    }
23
102
    true
24
986
}
25

            
26
/// True if the entire AST is constants.
27
200124
pub fn is_all_constant(expression: &Expr) -> bool {
28
201518
    for atom in <Expr as Biplate<Atom>>::universe_bi(expression) {
29
201518
        match atom {
30
41140
            Atom::Literal(_) => {}
31
            Atom::Reference(_) => {
32
160378
                return false;
33
            }
34
        }
35
    }
36

            
37
39746
    true
38
200124
}
39

            
40
/// Creates a single `Expr` from a `Vec<Expr>` by forming the conjunction.
41
///
42
/// If it contains a single element, that element is returned, otherwise the conjunction of the
43
/// elements is returned.
44
///
45
/// # Returns
46
///
47
/// - Some: the expression list is non empty, and all expressions are booleans (so can be
48
/// conjuncted).
49
///
50
/// - None: the expression list is empty, or not all expressions are booleans.
51
340
pub fn exprs_to_conjunction(exprs: &Vec<Expr>) -> Option<Expr> {
52
340
    match exprs.as_slice() {
53
340
        [] => None,
54
340
        [a] if a.return_type() == Some(ReturnType::Bool) => Some(a.clone()),
55
        _ => {
56
            for expr in exprs {
57
                if expr.return_type() != Some(ReturnType::Bool) {
58
                    return None;
59
                }
60
            }
61
            Some(Expr::And(Metadata::new(), exprs.clone()))
62
        }
63
    }
64
340
}
65

            
66
/// Creates a new auxiliary variable using the given expression.
67
///
68
/// # Returns
69
///
70
/// * `None` if `Expr` is a `Atom`, or `Expr` does not have a domain (for example, if it is a `Bubble`).
71
///
72
/// * `Some(ToAuxVarOutput)` if successful, containing:
73
///     
74
///     + A new model, modified to include the auxiliary variable in the symbol table.
75
///     + A new top level expression, containing the declaration of the auxiliary variable.
76
///     + A reference to the auxiliary variable to replace the existing expression with.
77
///
78
#[instrument]
79
pub fn to_aux_var(expr: &Expr, m: &Model) -> Option<ToAuxVarOutput> {
80
    let mut m = m.clone();
81

            
82
    // No need to put an atom in an aux_var
83
    if is_atom(expr) {
84
        return None;
85
    }
86

            
87
    // Anything that should be bubbled, bubble
88
    if (expr.can_be_undefined()) {
89
        return None;
90
    }
91

            
92
    let name = m.gensym();
93

            
94
    let Some(domain) = expr.domain_of(&m.variables) else {
95
        tracing::trace!("could not find domain of {}", expr);
96
        return None;
97
    };
98

            
99
    m.add_variable(name.clone(), DecisionVariable::new(domain.clone()));
100

            
101
    Some(ToAuxVarOutput {
102
        aux_name: name.clone(),
103
        aux_decl: Expr::AuxDeclaration(Metadata::new(), name, Box::new(expr.clone())),
104
        aux_domain: domain,
105
        new_model: m,
106
        _unconstructable: (),
107
    })
108
}
109

            
110
/// Output data of `to_aux_var`.
111
pub struct ToAuxVarOutput {
112
    aux_name: Name,
113
    aux_decl: Expr,
114
    aux_domain: Domain,
115
    new_model: Model,
116
    _unconstructable: (),
117
}
118

            
119
impl ToAuxVarOutput {
120
    /// Returns the new auxiliary variable as an `Atom`.
121
578
    pub fn as_atom(&self) -> Atom {
122
578
        Atom::Reference(self.aux_name())
123
578
    }
124

            
125
    /// Returns the new auxiliary variable as an `Expression`.
126
    ///
127
    /// This expression will have default `Metadata`.
128
578
    pub fn as_expr(&self) -> Expr {
129
578
        Expr::Atomic(Metadata::new(), self.as_atom())
130
578
    }
131

            
132
    /// Returns the top level `Expression` to add to the model.
133
578
    pub fn top_level_expr(&self) -> Expr {
134
578
        self.aux_decl.clone()
135
578
    }
136

            
137
    /// Returns the new `Model`, modified to contain this auxiliary variable in the symbol table.
138
    ///
139
    /// Like `Reduction`, this new model does not include the new top level expression. To get
140
    /// this, use [`top_level_expr()`](`ToAuxVarOutput::top_level_expr()`).
141
578
    pub fn model(&self) -> Model {
142
578
        self.new_model.clone()
143
578
    }
144

            
145
    /// Returns the name of the auxiliary variable.
146
578
    pub fn aux_name(&self) -> Name {
147
578
        self.aux_name.clone()
148
578
    }
149
}