1
//! Normalisation rules for comprehensions.
2

            
3
use std::collections::HashSet;
4

            
5
use conjure_cp::{
6
    ast::{
7
        Expression as Expr, Metadata, Moo, Name, SymbolTable, SymbolTablePtr,
8
        ac_operators::ACOperatorKind, comprehension::Comprehension,
9
    },
10
    rule_engine::{
11
        ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect, register_rule,
12
    },
13
};
14

            
15
/// Merges nested comprehensions inside the same AC operator into a single comprehension.
16
///
17
/// ```text
18
/// op([ op([ op([ body | qs3 ]) | qs2 ]) | qs1 ]) ~> op([ body | qs1, qs2, qs3 ])
19
/// ```
20
///
21
/// where `op` is one of `and`, `or`, `sum`, or `product`.
22
#[register_rule(
23
    "Base",
24
    8900,
25
    [
26
        And / Comprehension,
27
        Or / Comprehension,
28
        Sum / Comprehension,
29
        Product / Comprehension
30
    ]
31
)]
32
252021
fn merge_nested_ac_comprehensions(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
33
252021
    let new_expr = merge_nested_ac_comprehensions_impl(expr).ok_or(RuleNotApplicable)?;
34
5349
    Ok(RuleEffect::pure(new_expr))
35
252021
}
36

            
37
252021
fn merge_nested_ac_comprehensions_impl(expr: &Expr) -> Option<Expr> {
38
252021
    let ac_operator_kind = expr.to_ac_operator_kind()?;
39

            
40
247196
    let outer_comprehension = match expr {
41
        Expr::And(_, child)
42
        | Expr::Or(_, child)
43
        | Expr::Sum(_, child)
44
        | Expr::Product(_, child) => {
45
247196
            let Expr::Comprehension(_, comprehension) = child.as_ref() else {
46
570
                return None;
47
            };
48
246626
            comprehension.as_ref().clone()
49
        }
50
        _ => return None,
51
    };
52

            
53
246626
    let parent_scope = outer_comprehension.symbols().parent().clone()?;
54

            
55
246626
    let mut merged_levels = vec![outer_comprehension.clone()];
56
246626
    let mut merged_names: HashSet<Name> = outer_comprehension
57
246626
        .quantified_vars()
58
246626
        .iter()
59
246626
        .cloned()
60
246626
        .collect();
61

            
62
246626
    let mut current_return_expression = outer_comprehension.return_expression();
63
251975
    while let Some(inner_comprehension) =
64
251975
        extract_inner_comprehension(ac_operator_kind, &current_return_expression)
65
    {
66
        // Avoid changing semantics when inner quantifiers shadow outer ones.
67
5349
        if inner_comprehension
68
5349
            .quantified_vars()
69
5349
            .iter()
70
5412
            .any(|name| merged_names.contains(name))
71
        {
72
            break;
73
5349
        }
74

            
75
5349
        merged_names.extend(inner_comprehension.quantified_vars().iter().cloned());
76
5349
        current_return_expression = inner_comprehension.clone().return_expression();
77
5349
        merged_levels.push(inner_comprehension);
78
    }
79

            
80
246626
    if merged_levels.len() < 2 {
81
241277
        return None;
82
5349
    }
83

            
84
5349
    let merged_symbols = merge_symbols(parent_scope, &merged_levels);
85
5349
    let merged_qualifiers = merged_levels
86
5349
        .iter()
87
10698
        .flat_map(|level| level.qualifiers.clone())
88
        .collect();
89
5349
    let mut merged = merged_levels.first()?.clone();
90
5349
    merged.return_expression = current_return_expression;
91
5349
    merged.qualifiers = merged_qualifiers;
92
5349
    merged.symbols = merged_symbols;
93

            
94
5349
    let merged_comprehension = Expr::Comprehension(Metadata::new(), Moo::new(merged));
95
5349
    let wrapped = match ac_operator_kind {
96
3684
        ACOperatorKind::And => Expr::And(Metadata::new(), Moo::new(merged_comprehension)),
97
1022
        ACOperatorKind::Or => Expr::Or(Metadata::new(), Moo::new(merged_comprehension)),
98
643
        ACOperatorKind::Sum => Expr::Sum(Metadata::new(), Moo::new(merged_comprehension)),
99
        ACOperatorKind::Product => Expr::Product(Metadata::new(), Moo::new(merged_comprehension)),
100
        // `expr.to_ac_operator_kind()` above never returns Min/Max (they aren't in
101
        // `TryFrom<&Expression> for ACOperatorKind`, deliberately -- see that type's doc comment),
102
        // so `ac_operator_kind` can never actually be one of these here.
103
        ACOperatorKind::Min | ACOperatorKind::Max => unreachable!(),
104
    };
105

            
106
5349
    Some(wrapped)
107
252021
}
108

            
109
251975
fn extract_inner_comprehension(
110
251975
    ac_operator_kind: ACOperatorKind,
111
251975
    expr: &Expr,
112
251975
) -> Option<Comprehension> {
113
251975
    let wrapped = match (ac_operator_kind, expr) {
114
7136
        (ACOperatorKind::And, Expr::And(_, child)) => child.as_ref(),
115
1022
        (ACOperatorKind::Or, Expr::Or(_, child)) => child.as_ref(),
116
651
        (ACOperatorKind::Sum, Expr::Sum(_, child)) => child.as_ref(),
117
        (ACOperatorKind::Product, Expr::Product(_, child)) => child.as_ref(),
118
243166
        _ => return None,
119
    };
120

            
121
8809
    as_single_comprehension(wrapped)
122
251975
}
123

            
124
8809
fn as_single_comprehension(expr: &Expr) -> Option<Comprehension> {
125
8809
    if let Expr::Comprehension(_, comprehension) = expr {
126
5349
        return Some(comprehension.as_ref().clone());
127
3460
    }
128

            
129
3460
    let exprs = expr.clone().into_list()?;
130
3392
    let [Expr::Comprehension(_, comprehension)] = exprs.as_slice() else {
131
3392
        return None;
132
    };
133

            
134
    Some(comprehension.as_ref().clone())
135
8809
}
136

            
137
5349
fn merge_symbols(parent_scope: SymbolTablePtr, levels: &[Comprehension]) -> SymbolTablePtr {
138
5349
    let symbols = SymbolTablePtr::with_parent(parent_scope);
139
5349
    for level in levels {
140
10698
        let level_symbols = level.symbols();
141
10698
        for (_, decl) in level_symbols.iter_local() {
142
10782
            symbols.write().update_insert(decl.clone());
143
        }
144
    }
145
5349
    symbols
146
5349
}