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::trace;
17use uniplate::Biplate;
18
19pub 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 while done_something {
46 let mut new_model = None;
47 done_something = false;
48
49 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
87fn 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 'top: for (priority, rules) in rules_grouped.iter() {
101 for (expr, ctx) in submodel_ctx(submodel.clone()) {
104 let expr = expr.clone();
106 let ctx = ctx.clone();
107 for rd in rules {
108 stats.rewriter_rule_application_attempts =
110 Some(stats.rewriter_rule_application_attempts.unwrap_or(0) + 1);
111
112 match (rd.rule.application)(&expr, &submodel.symbols()) {
113 Ok(red) => {
114 stats.rewriter_rule_applications =
116 Some(stats.rewriter_rule_applications.unwrap_or(0) + 1);
117
118 results.push((
120 RuleResult {
121 rule_data: rd.clone(),
122 reduction: red,
123 },
124 *priority,
125 expr.clone(),
126 ctx.clone(),
127 ));
128 }
129 Err(_) => {
130 #[cfg(debug_assertions)]
132 tracing::trace!(
133 "Rule attempted but not applied: {} (priority {}, rule set {}), to expression: {}",
134 rd.rule.name,
135 priority,
136 rd.rule_set.name,
137 expr
138 );
139 }
140 }
141 }
142 if !results.is_empty() {
145 break 'top;
146 }
147 }
148 }
149
150 match results.as_slice() {
151 [] => {
152 return None;
153 } [(result, _priority, expr, ctx), ..] => {
155 if prop_multiple_equally_applicable {
156 assert_no_multiple_equally_applicable_rules(&results, rules_grouped);
157 }
158
159 log_rule_application(result, expr, submodel);
161
162 *submodel = ctx(result.reduction.new_expression.clone());
164
165 result.reduction.clone().apply(submodel);
167 }
168 }
169
170 Some(())
171}
172
173fn assert_no_multiple_equally_applicable_rules<CtxFnType>(
175 results: &Vec<(RuleResult<'_>, u16, Expr, CtxFnType)>,
176 rules_grouped: &Vec<(u16, Vec<RuleData<'_>>)>,
177) {
178 if results.len() <= 1 {
179 return;
180 }
181
182 let names: Vec<_> = results
183 .iter()
184 .map(|(result, _, _, _)| result.rule_data.rule.name)
185 .collect();
186
187 let expr = results[0].2.clone();
189
190 let mut rules_by_priority_string = String::new();
192 rules_by_priority_string.push_str("Rules grouped by priority:\n");
193 for (priority, rules) in rules_grouped.iter() {
194 rules_by_priority_string.push_str(&format!("Priority {}:\n", priority));
195 for rd in rules {
196 rules_by_priority_string.push_str(&format!(
197 " - {} (from {})\n",
198 rd.rule.name, rd.rule_set.name
199 ));
200 }
201 }
202 bug!("Multiple equally applicable rules for {expr}: {names:#?}\n\n{rules_by_priority_string}");
203}