conjure_core/rule_engine/
rewrite_naive.rs

1use super::{resolve_rules::RuleData, RewriteError, RuleSet};
2use crate::{
3    ast::{Expression as Expr, SubModel},
4    bug,
5    rule_engine::{
6        get_rules_grouped,
7        rewriter_common::{log_rule_application, RuleResult},
8        submodel_zipper::submodel_ctx,
9    },
10    stats::RewriterStats,
11    Model,
12};
13
14use itertools::Itertools;
15use std::{sync::Arc, time::Instant};
16use tracing::{span, trace, Level};
17use uniplate::Biplate;
18
19/// A naive, exhaustive rewriter for development purposes. Applies rules in priority order,
20/// favouring expressions found earlier during preorder traversal of the tree.
21pub fn rewrite_naive<'a>(
22    model: &Model,
23    rule_sets: &Vec<&'a RuleSet<'a>>,
24    prop_multiple_equally_applicable: bool,
25) -> Result<Model, RewriteError> {
26    let rules_grouped = get_rules_grouped(rule_sets)
27        .unwrap_or_else(|_| bug!("get_rule_priorities() failed!"))
28        .into_iter()
29        .collect_vec();
30
31    let mut model = model.clone();
32    let mut done_something = true;
33
34    let mut rewriter_stats = RewriterStats::new();
35    rewriter_stats.is_optimization_enabled = Some(false);
36    let run_start = Instant::now();
37
38    trace!(
39        target: "rule_engine_human",
40        "Model before rewriting:\n\n{}\n--\n",
41        model
42    );
43
44    // Rewrite until there are no more rules left to apply.
45    while done_something {
46        let mut new_model = None;
47        done_something = false;
48
49        // Rewrite each sub-model in the tree, largest first.
50        for (mut submodel, ctx) in <_ as Biplate<SubModel>>::contexts_bi(&model) {
51            if try_rewrite_model(
52                &mut submodel,
53                &rules_grouped,
54                prop_multiple_equally_applicable,
55                &mut rewriter_stats,
56            )
57            .is_some()
58            {
59                new_model = Some(ctx(submodel));
60                done_something = true;
61                break;
62            }
63        }
64        if let Some(new_model) = new_model {
65            model = new_model;
66        }
67    }
68
69    let run_end = Instant::now();
70    rewriter_stats.rewriter_run_time = Some(run_end - run_start);
71
72    model
73        .context
74        .write()
75        .unwrap()
76        .stats
77        .add_rewriter_run(rewriter_stats);
78
79    trace!(
80        target: "rule_engine_human",
81        "Final model:\n\n{}",
82        model
83    );
84    Ok(model)
85}
86
87// Tries to do a single rewrite on the model.
88//
89// Returns None if no change was made.
90fn try_rewrite_model(
91    submodel: &mut SubModel,
92    rules_grouped: &Vec<(u16, Vec<RuleData<'_>>)>,
93    prop_multiple_equally_applicable: bool,
94    stats: &mut RewriterStats,
95) -> Option<()> {
96    type CtxFn = Arc<dyn Fn(Expr) -> SubModel>;
97    let mut results: Vec<(RuleResult<'_>, u16, Expr, CtxFn)> = vec![];
98
99    // Iterate over rules by priority in descending order.
100    'top: for (priority, rules) in rules_grouped.iter() {
101        // Using Biplate, rewrite both the expression tree, and any value lettings in the symbol
102        // table.
103        for (expr, ctx) in submodel_ctx(submodel.clone()) {
104            // Clone expr and ctx so they can be reused
105            let expr = expr.clone();
106            let ctx = ctx.clone();
107            for rd in rules {
108                // Count rule application attempts
109                stats.rewriter_rule_application_attempts =
110                    Some(stats.rewriter_rule_application_attempts.unwrap_or(0) + 1);
111
112                #[cfg(debug_assertions)]
113                let span = span!(Level::TRACE,"trying_rule_application",rule_name=rd.rule.name,rule_target_expression=%expr);
114
115                #[cfg(debug_assertions)]
116                let _guard = span.enter();
117
118                #[cfg(debug_assertions)]
119                tracing::trace!(rule_name = rd.rule.name, "Trying rule");
120
121                match (rd.rule.application)(&expr, &submodel.symbols()) {
122                    Ok(red) => {
123                        // Count successful rule applications
124                        stats.rewriter_rule_applications =
125                            Some(stats.rewriter_rule_applications.unwrap_or(0) + 1);
126
127                        // Collect applicable rules
128                        results.push((
129                            RuleResult {
130                                rule_data: rd.clone(),
131                                reduction: red,
132                            },
133                            *priority,
134                            expr.clone(),
135                            ctx.clone(),
136                        ));
137                    }
138                    Err(_) => {
139                        // when called a lot, this becomes very expensive!
140                        #[cfg(debug_assertions)]
141                        tracing::trace!(
142                            "Rule attempted but not applied: {} (priority {}, rule set {}), to expression: {}",
143                            rd.rule.name,
144                            priority,
145                            rd.rule_set.name,
146                            expr
147                        );
148                    }
149                }
150            }
151            // This expression has the highest rule priority so far, so this is what we want to
152            // rewrite.
153            if !results.is_empty() {
154                break 'top;
155            }
156        }
157    }
158
159    match results.as_slice() {
160        [] => {
161            return None;
162        } // no rules are applicable.
163        [(result, _priority, expr, ctx), ..] => {
164            if prop_multiple_equally_applicable {
165                assert_no_multiple_equally_applicable_rules(&results, rules_grouped);
166            }
167
168            // Extract the single applicable rule and apply it
169            log_rule_application(result, expr, submodel);
170
171            // Replace expr with new_expression
172            *submodel = ctx(result.reduction.new_expression.clone());
173
174            // Apply new symbols and top level
175            result.reduction.clone().apply(submodel);
176        }
177    }
178
179    Some(())
180}
181
182// Exits with a bug if there are multiple equally applicable rules for an expression.
183fn assert_no_multiple_equally_applicable_rules<CtxFnType>(
184    results: &Vec<(RuleResult<'_>, u16, Expr, CtxFnType)>,
185    rules_grouped: &Vec<(u16, Vec<RuleData<'_>>)>,
186) {
187    if results.len() <= 1 {
188        return;
189    }
190
191    let names: Vec<_> = results
192        .iter()
193        .map(|(result, _, _, _)| result.rule_data.rule.name)
194        .collect();
195
196    // Extract the expression from the first result
197    let expr = results[0].2.clone();
198
199    // Construct a single string to display the names of the rules grouped by priority
200    let mut rules_by_priority_string = String::new();
201    rules_by_priority_string.push_str("Rules grouped by priority:\n");
202    for (priority, rules) in rules_grouped.iter() {
203        rules_by_priority_string.push_str(&format!("Priority {}:\n", priority));
204        for rd in rules {
205            rules_by_priority_string.push_str(&format!(
206                "  - {} (from {})\n",
207                rd.rule.name, rd.rule_set.name
208            ));
209        }
210    }
211    bug!("Multiple equally applicable rules for {expr}: {names:#?}\n\n{rules_by_priority_string}");
212}