1
use super::{RewriteError, RuleSet, resolve_rules::RuleData};
2
use crate::{
3
    Model,
4
    ast::Expression as Expr,
5
    bug,
6
    rule_engine::{
7
        get_rules_grouped,
8
        rewriter_common::{
9
            RuleResult, VariableDeclarationSnapshot, log_rule_application,
10
            snapshot_variable_declarations,
11
        },
12
        submodel_zipper::expression_ctx,
13
    },
14
    settings::{Rewriter, set_current_rewriter},
15
    stats::RewriterStats,
16
};
17

            
18
use itertools::Itertools;
19
use std::{sync::Arc, time::Instant};
20
use tracing::trace;
21

            
22
// debug imports
23
#[cfg(debug_assertions)]
24
use {
25
    crate::ast::assertions::debug_assert_model_well_formed,
26
    tracing::{Level, span},
27
};
28

            
29
type VariableSnapshots = Option<(VariableDeclarationSnapshot, VariableDeclarationSnapshot)>;
30
type ApplicableRule<'a, CtxFnType> = (RuleResult<'a>, u16, Expr, CtxFnType, VariableSnapshots);
31

            
32
/// A naive, exhaustive rewriter for development purposes. Applies rules in priority order,
33
/// favouring expressions found earlier during preorder traversal of the tree.
34
11242
pub fn rewrite_naive<'a>(
35
11242
    model: &Model,
36
11242
    rule_sets: &Vec<&'a RuleSet<'a>>,
37
11242
    prop_multiple_equally_applicable: bool,
38
11242
) -> Result<Model, RewriteError> {
39
11242
    set_current_rewriter(Rewriter::Naive);
40

            
41
11242
    let rules_grouped = get_rules_grouped(rule_sets)
42
        .unwrap_or_else(|_| bug!("get_rule_priorities() failed!"))
43
11242
        .into_iter()
44
11242
        .collect_vec();
45

            
46
11242
    let mut model = model.clone();
47
11242
    let mut done_something = true;
48

            
49
11242
    let mut rewriter_stats = RewriterStats::new();
50
11242
    rewriter_stats.is_optimization_enabled = Some(false);
51
11242
    let run_start = Instant::now();
52

            
53
11242
    trace!(
54
        target: "rule_engine_human",
55
        "Model before rewriting:\n\n{}\n--\n",
56
        model
57
    );
58
11242
    trace!(
59
        target: "rule_engine_human_verbose",
60
        "elapsed_s,rule_level,rule_name,rule_set,status,expression"
61
    );
62

            
63
    // Rewrite until there are no more rules left to apply.
64
172952
    while done_something {
65
161710
        done_something = try_rewrite_model(
66
161710
            &mut model,
67
161710
            &rules_grouped,
68
161710
            prop_multiple_equally_applicable,
69
161710
            &mut rewriter_stats,
70
161710
            &run_start,
71
161710
        )
72
161710
        .is_some();
73
161710
    }
74

            
75
11242
    let run_end = Instant::now();
76
11242
    rewriter_stats.rewriter_run_time = Some(run_end - run_start);
77

            
78
11242
    model
79
11242
        .context
80
11242
        .write()
81
11242
        .unwrap()
82
11242
        .stats
83
11242
        .add_rewriter_run(rewriter_stats);
84

            
85
11242
    trace!(
86
        target: "rule_engine_human",
87
        "Final model:\n\n{}",
88
        model
89
    );
90
11242
    Ok(model)
91
11242
}
92

            
93
// Tries to do a single rewrite on the model.
94
//
95
// Returns None if no change was made.
96
161710
fn try_rewrite_model(
97
161710
    submodel: &mut Model,
98
161710
    rules_grouped: &Vec<(u16, Vec<RuleData<'_>>)>,
99
161710
    prop_multiple_equally_applicable: bool,
100
161710
    stats: &mut RewriterStats,
101
161710
    run_start: &Instant,
102
161710
) -> Option<()> {
103
    type CtxFn = Arc<dyn Fn(Expr) -> Expr>;
104
161710
    let mut results: Vec<ApplicableRule<'_, CtxFn>> = vec![];
105

            
106
    // Iterate over rules by priority in descending order.
107
1445726
    'top: for (priority, rules) in rules_grouped.iter() {
108
        // Rewrite within the current root expression tree.
109
39253320
        for (expr, ctx) in expression_ctx(submodel.root().clone()) {
110
            // Clone expr and ctx so they can be reused
111
39253320
            let expr = expr.clone();
112
39253320
            let ctx = ctx.clone();
113
159403520
            for rd in rules {
114
                // Count rule application attempts
115
159403520
                stats.rewriter_rule_application_attempts =
116
159403520
                    Some(stats.rewriter_rule_application_attempts.unwrap_or(0) + 1);
117

            
118
                #[cfg(debug_assertions)]
119
159403520
                let span = span!(Level::TRACE,"trying_rule_application",rule_name=rd.rule.name,rule_target_expression=%expr);
120

            
121
                #[cfg(debug_assertions)]
122
159403520
                let _guard = span.enter();
123

            
124
                #[cfg(debug_assertions)]
125
159403520
                tracing::trace!(rule_name = rd.rule.name, "Trying rule");
126

            
127
159403520
                let before_variable_snapshot = matches!(expr, Expr::Root(_, _))
128
159403520
                    .then(|| snapshot_variable_declarations(&submodel.symbols()));
129

            
130
159403520
                match (rd.rule.application)(&expr, &submodel.symbols()) {
131
150548
                    Ok(red) => {
132
150548
                        log_verbose_rule_attempt(
133
150548
                            run_start,
134
150548
                            priority,
135
150548
                            rd.rule.name,
136
150548
                            rd.rule_set.name,
137
150548
                            "success",
138
150548
                            &expr,
139
                        );
140

            
141
                        // Count successful rule applications
142
150548
                        stats.rewriter_rule_applications =
143
150548
                            Some(stats.rewriter_rule_applications.unwrap_or(0) + 1);
144

            
145
150548
                        let after_variable_snapshot = before_variable_snapshot
146
150548
                            .as_ref()
147
150548
                            .map(|_| snapshot_variable_declarations(&red.symbols));
148
150548
                        let variable_snapshots =
149
150548
                            before_variable_snapshot.zip(after_variable_snapshot);
150

            
151
                        // Collect applicable rules
152
150548
                        results.push((
153
150548
                            RuleResult {
154
150548
                                rule_data: rd.clone(),
155
150548
                                reduction: red,
156
150548
                            },
157
150548
                            *priority,
158
150548
                            expr.clone(),
159
150548
                            ctx.clone(),
160
150548
                            variable_snapshots,
161
150548
                        ));
162
                    }
163
                    Err(_) => {
164
159252972
                        log_verbose_rule_attempt(
165
159252972
                            run_start,
166
159252972
                            priority,
167
159252972
                            rd.rule.name,
168
159252972
                            rd.rule_set.name,
169
159252972
                            "fail",
170
159252972
                            &expr,
171
                        );
172

            
173
                        // when called a lot, this becomes very expensive!
174
                        #[cfg(debug_assertions)]
175
159252972
                        tracing::trace!(
176
                            "Rule attempted but not applied: {} (priority {}, rule set {}), to expression: {}",
177
                            rd.rule.name,
178
                            priority,
179
                            rd.rule_set.name,
180
                            expr
181
                        );
182
                    }
183
                }
184
            }
185
            // This expression has the highest rule priority so far, so this is what we want to
186
            // rewrite.
187
39253320
            if !results.is_empty() {
188
150468
                break 'top;
189
39102852
            }
190
        }
191
    }
192

            
193
161710
    match results.as_slice() {
194
161710
        [] => {
195
11242
            return None;
196
        } // no rules are applicable.
197
150468
        [(result, _priority, expr, ctx, variable_snapshots), ..] => {
198
150468
            if prop_multiple_equally_applicable {
199
100
                assert_no_multiple_equally_applicable_rules(&results, rules_grouped);
200
150368
            }
201

            
202
            // Extract the single applicable rule and apply it
203
150468
            log_rule_application(
204
150468
                result,
205
150468
                expr,
206
150468
                &submodel.symbols(),
207
150468
                variable_snapshots
208
150468
                    .as_ref()
209
150468
                    .map(|(before, after)| (before, after)),
210
            );
211

            
212
            // Replace expr with new_expression
213
150468
            let new_root = ctx(result.reduction.new_expression.clone());
214
150468
            submodel.replace_root(new_root);
215

            
216
            // Apply new symbols and top level
217
150468
            result.reduction.clone().apply(submodel);
218

            
219
            #[cfg(debug_assertions)]
220
150468
            {
221
150468
                let assertion_context = format!(
222
150468
                    "naive rewriter after applying rule '{}'",
223
150468
                    result.rule_data.rule.name
224
150468
                );
225
150468
                debug_assert_model_well_formed(submodel, &assertion_context);
226
150468
            }
227
        }
228
    }
229

            
230
150468
    Some(())
231
161710
}
232

            
233
3432432
fn csv_escape(field: &str) -> String {
234
3432432
    if field.contains([',', '"', '\n', '\r']) {
235
461042
        format!("\"{}\"", field.replace('"', "\"\""))
236
    } else {
237
2971390
        field.to_string()
238
    }
239
3432432
}
240

            
241
159403520
fn log_verbose_rule_attempt(
242
159403520
    run_start: &Instant,
243
159403520
    priority: &u16,
244
159403520
    rule_name: &str,
245
159403520
    rule_set_name: &str,
246
159403520
    status: &str,
247
159403520
    expr: &Expr,
248
159403520
) {
249
159403520
    let elapsed_seconds = run_start.elapsed().as_secs_f64();
250
159403520
    let expr_str = expr.to_string();
251
159403520
    trace!(
252
        target: "rule_engine_human_verbose",
253
        "{:.3},{},{},{},{},{}",
254
        elapsed_seconds,
255
        priority,
256
1144144
        csv_escape(rule_name),
257
1144144
        csv_escape(rule_set_name),
258
        status,
259
1144144
        csv_escape(&expr_str)
260
    );
261
159403520
}
262

            
263
// Exits with a bug if there are multiple equally applicable rules for an expression.
264
100
fn assert_no_multiple_equally_applicable_rules<CtxFnType>(
265
100
    results: &Vec<ApplicableRule<'_, CtxFnType>>,
266
100
    rules_grouped: &Vec<(u16, Vec<RuleData<'_>>)>,
267
100
) {
268
100
    if results.len() <= 1 {
269
100
        return;
270
    }
271

            
272
    let names: Vec<_> = results
273
        .iter()
274
        .map(|(result, _, _, _, _)| result.rule_data.rule.name)
275
        .collect();
276

            
277
    // Extract the expression from the first result
278
    let expr = results[0].2.clone();
279

            
280
    // Construct a single string to display the names of the rules grouped by priority
281
    let mut rules_by_priority_string = String::new();
282
    rules_by_priority_string.push_str("Rules grouped by priority:\n");
283
    for (priority, rules) in rules_grouped.iter() {
284
        rules_by_priority_string.push_str(&format!("Priority {priority}:\n"));
285
        for rd in rules {
286
            rules_by_priority_string.push_str(&format!(
287
                "  - {} (from {})\n",
288
                rd.rule.name, rd.rule_set.name
289
            ));
290
        }
291
    }
292
    bug!("Multiple equally applicable rules for {expr}: {names:#?}\n\n{rules_by_priority_string}");
293
100
}