1use std::collections::{BTreeMap, HashSet};
4
5use crate::kind::NodeKind;
6use crate::rule::{apply_edit, Edit, RewriteCtx, Rule};
7use crate::walk::{walk, Walk};
8use crate::lang::Lang;
9
10#[derive(Clone, Debug)]
11pub struct Config {
12 pub max_edits: usize,
14 pub disabled_rules: HashSet<String>,
16 pub remove_dead_methods: bool,
20}
21
22impl Default for Config {
23 fn default() -> Self {
24 Config {
25 max_edits: 100_000,
26 disabled_rules: HashSet::new(),
27 remove_dead_methods: false,
28 }
29 }
30}
31
32impl Config {
33 pub fn enabled(&self, rule: &str) -> bool {
34 !self.disabled_rules.contains(rule)
35 }
36}
37
38#[derive(Debug, Default)]
39pub struct Report {
40 pub edits: usize,
42 pub iterations: usize,
44 pub discarded: usize,
47 pub by_rule: BTreeMap<&'static str, usize>,
49}
50
51
52fn legalize_stmt_fold<L: Lang>(
55 lang: &L,
56 snap: &Walk<L>,
57 id: L::Id,
58 edit: Edit<L>,
59) -> Option<Edit<L>> {
60 let Edit::Replace { target, with } = &edit else { return Some(edit) };
61 if *target != id || lang.kind(*with) != NodeKind::Literal {
62 return Some(edit);
63 }
64 if !matches!(lang.kind(id), NodeKind::Call | NodeKind::New) {
67 return Some(edit);
68 }
69 let Some(&(parent, _)) = snap.parents.get(&id) else { return Some(edit) };
70 if lang.kind(parent) != NodeKind::ExprStmt || lang.children(parent).len() != 1 {
71 return Some(edit);
72 }
73 let Some(&(grand, _)) = snap.parents.get(&parent) else { return Some(edit) };
77 if lang.kind(grand) != NodeKind::Block {
78 return None; }
80 Some(Edit::Delete { node: parent })
81}
82
83
84pub fn fold_root<L: Lang>(
93 lang: &mut L,
94 root: L::Id,
95 rules: &[Box<dyn Rule<L>>],
96 cfg: &Config,
97) -> Option<(L::Id, &'static str)> {
98 for rule in rules {
99 if !cfg.enabled(rule.name()) {
100 continue;
101 }
102 let snap = walk(lang, root);
103 let ctx = RewriteCtx { lang: &mut *lang, walk: &snap };
104 if let Some(edit) = rule.check(ctx, root) {
105 if let Edit::Replace { target, with } = edit {
106 if target == root {
107 let red = Edit::Replace { target, with }.cost_reduction(lang);
108 if red > 0 {
109 return Some((with, rule.name()));
110 }
111 }
112 }
113 }
114 }
115 None
116}
117
118pub fn simplify<L: Lang>(
131 lang: &mut L,
132 root: L::Id,
133 rules: &[Box<dyn Rule<L>>],
134 cfg: &Config,
135) -> Report {
136 let mut report = Report::default();
137 lang.prepare(root);
138 let mut dispatch: Vec<Option<Box<[u32]>>> = vec![None; NodeKind::SLOT_COUNT];
142 loop {
143 let mut snap = walk(lang, root);
144 let mut touched: Vec<u64> = Vec::new();
148 let mut moved: Vec<u64> = Vec::new();
152 struct Queued<L: Lang> {
153 #[allow(dead_code)]
156 rule_idx: usize,
157 #[allow(dead_code)]
158 node: L::Id,
159 rule: &'static str,
160 edit: Edit<L>,
161 expects: Vec<Vec<L::Id>>,
162 }
163 let mut queued: Vec<Queued<L>> = Vec::new();
164 let mut applied = false;
165 #[allow(unused_mut)]
166 let applied_immediate = false;
167
168 'scan: for &id in &snap.ids {
169 if bit_get(&touched, lang.node_index(id)) {
170 continue;
171 }
172 let kind = lang.kind(id);
173 let ki = kind.slot();
174 debug_assert!(ki < NodeKind::SLOT_COUNT);
175 let bucket: &[u32] = {
176 let slot = &mut dispatch[ki];
177 if slot.is_none() {
178 *slot = Some(
179 rules
180 .iter()
181 .enumerate()
182 .filter(|(_, r)| {
183 let ks = r.kinds();
184 ks.is_empty() || ks.contains(&kind)
185 })
186 .map(|(i, _)| i as u32)
187 .collect::<Vec<u32>>()
188 .into_boxed_slice(),
189 );
190 }
191 slot.as_deref().unwrap()
192 };
193 for &ri in bucket {
194 let rule = &rules[ri as usize];
195 if !cfg.enabled(rule.name()) {
196 continue;
197 }
198 let ctx = RewriteCtx {
199 lang: &mut *lang,
200 walk: &snap,
201 };
202 let proposal = rule.check(ctx, id);
203 let Some(edit) = proposal else {
204 continue;
205 };
206 let Some(edit) = legalize_stmt_fold(&*lang, &snap, id, edit) else {
211 continue;
212 };
213 let structural = rule.structural();
214 let reduction = edit.cost_reduction(lang);
215 debug_assert!(
216 structural || reduction > 0,
217 "rule `{}` proposed a non-simplifying edit",
218 rule.name()
219 );
220 if !structural && reduction == 0 {
221 continue;
222 }
223 if crate::rule::is_replace_only(&edit) {
224 let mut fp = Vec::new();
227 crate::rule::collect_footprint(&*lang, &edit, &mut fp);
228 if fp
229 .iter()
230 .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
231 {
232 continue;
233 }
234 if apply_edit(lang, &snap, &edit).is_ok() {
235 let mut stack = with_roots_of(&edit);
237 while let Some(n) = stack.pop() {
238 if n != snap.root && !snap.parents.contains_key(&n) {
239 bit_set(&mut moved, lang.node_index(n));
240 }
241 for &c in lang.children(n) {
242 stack.push(c);
243 }
244 }
245 if std::env::var("CURE_DEBUG_PASS").is_ok() {
246 eprintln!(
247 "[pass] immediate {} :: {}",
248 rule.name(),
249 describe_edit(&*lang, &edit)
250 );
251 }
252 *report.by_rule.entry(rule.name()).or_insert(0) += 1;
253 report.edits += 1;
254 applied = true;
255 for n in fp {
256 bit_set(&mut touched, lang.node_index(n));
257 }
258 let roots = {
269 let mut r = vec![id];
270 r.extend(edit_invalidation_roots(&edit));
271 r
272 };
273 for root in roots {
274 lang.invalidate_effect(root);
279 let mut cur = Some(root);
280 while let Some(a) =
281 cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
282 {
283 lang.invalidate_effect(a);
284 cur = Some(a);
285 }
286 }
287 continue 'scan;
288 }
289 } else {
290 let mut fp = Vec::new();
293 crate::rule::collect_footprint(&*lang, &edit, &mut fp);
294 if fp
295 .iter()
296 .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
297 {
298 continue;
299 }
300 let mut expects = Vec::new();
301 crate::rule::collect_splice_expectations(&*lang, &edit, &mut expects);
302 queued.push(Queued {
303 rule_idx: ri as usize,
304 node: id,
305 rule: rule.name(),
306 edit,
307 expects,
308 });
309 for n in fp {
310 bit_set(&mut touched, lang.node_index(n));
311 }
312 continue 'scan;
313 }
314 }
315 }
316
317 if !queued.is_empty() {
326 if applied_immediate {
327 snap = walk(lang, root);
328 }
329 queued.sort_by(|a, b| {
330 let ka = crate::rule::structural_sort_key(&snap, &a.edit);
331 let kb = crate::rule::structural_sort_key(&snap, &b.edit);
332 kb.cmp(&ka)
333 });
334 for q in queued {
335 let mut expects = q.expects.as_slice();
336 let ok = crate::rule::verify_deferred(&*lang, &snap, &q.edit, &mut expects);
337 if std::env::var("CURE_DEBUG_PASS").is_ok() {
338 eprintln!(
339 "[pass] deferred {} {} :: {}",
340 if ok { "apply" } else { "DISCARD" },
341 q.rule,
342 describe_edit(&*lang, &q.edit)
343 );
344 }
345 if ok {
346 let roots = edit_invalidation_roots(&q.edit);
350 crate::rule::apply_deferred(&mut *lang, &snap, &q.edit);
351 for root in roots {
352 lang.invalidate_effect(root);
353 let mut cur = Some(root);
354 while let Some(a) =
355 cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
356 {
357 lang.invalidate_effect(a);
358 cur = Some(a);
359 }
360 }
361 *report.by_rule.entry(q.rule).or_insert(0) += 1;
362 report.edits += 1;
363 applied = true;
364 } else {
365 report.discarded += 1;
366 }
367 }
368 }
369
370 report.iterations += 1;
371 if !applied || report.edits >= cfg.max_edits {
372 break;
373 }
374 lang.prepare(root);
375 }
376 report
377}
378
379fn bit_set(bits: &mut Vec<u64>, idx: usize) {
387 if idx >= bits.len() * 64 {
388 bits.resize(idx / 64 + 1, 0);
389 }
390 bits[idx / 64] |= 1u64 << (idx % 64);
391}
392
393fn bit_get(bits: &[u64], idx: usize) -> bool {
395 bits.get(idx / 64)
396 .is_some_and(|w| (w >> (idx % 64)) & 1 == 1)
397}
398
399fn edit_invalidation_roots<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
407 let mut out = Vec::new();
408 collect_all(edit, &mut out);
409 out
410}
411
412fn collect_all<L: Lang>(edit: &Edit<L>, out: &mut Vec<L::Id>) {
413 match edit {
414 Edit::Replace { target, with } => {
415 out.push(*target);
416 out.push(*with);
417 }
418 Edit::Delete { node } => out.push(*node),
419 Edit::Splice { node, insert, .. } => {
420 out.push(*node);
421 out.extend(insert.iter().copied());
422 }
423 Edit::Multi(es) => {
424 for e in es {
425 collect_all(e, out);
426 }
427 }
428 }
429}
430
431fn with_roots_of<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
432 match edit {
433 Edit::Replace { with, .. } => vec![*with],
434 Edit::Multi(es) => es
435 .iter()
436 .filter_map(|e| match e {
437 Edit::Replace { with, .. } => Some(*with),
438 _ => None,
439 })
440 .collect(),
441 _ => Vec::new(),
442 }
443}
444
445fn describe_edit<L: Lang>(lang: &L, edit: &Edit<L>) -> String {
447 fn kind_of<L: Lang>(lang: &L, id: L::Id) -> &'static str {
448 match lang.kind(id) {
449 NodeKind::Block => "Block",
450 NodeKind::VarDecl => "VarDecl",
451 NodeKind::Assign => "Assign",
452 NodeKind::ExprStmt => "ExprStmt",
453 NodeKind::Return => "Return",
454 NodeKind::If => "If",
455 NodeKind::While => "While",
456 NodeKind::VarRef => "VarRef",
457 NodeKind::Literal => "Literal",
458 NodeKind::Binary => "Binary",
459 NodeKind::Call => "Call",
460 NodeKind::Index => "Index",
461 NodeKind::Member => "Member",
462 _ => "…",
463 }
464 }
465 fn go<L: Lang>(lang: &L, e: &Edit<L>, out: &mut String) {
466 match e {
467 Edit::Replace { target, with } => {
468 out.push_str(&format!(
469 "Replace {}→{} ",
470 kind_of(lang, *target),
471 kind_of(lang, *with)
472 ));
473 }
474 Edit::Delete { node } => {
475 out.push_str(&format!("Delete {} ", kind_of(lang, *node)));
476 }
477 Edit::Splice { index, remove, .. } => {
478 out.push_str(&format!("Splice @{index} -{remove} "));
479 }
480 Edit::Multi(es) => {
481 out.push('[');
482 for e in es {
483 go(lang, e, out);
484 }
485 out.push(']');
486 }
487 }
488 }
489 let mut s = String::new();
490 go(lang, edit, &mut s);
491 s
492}