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 pub preserve_function_source: bool,
24}
25
26impl Default for Config {
27 fn default() -> Self {
28 Config {
29 max_edits: 100_000,
30 disabled_rules: HashSet::new(),
31 remove_dead_methods: false,
32 preserve_function_source: false,
33 }
34 }
35}
36
37impl Config {
38 pub fn enabled(&self, rule: &str) -> bool {
39 !self.disabled_rules.contains(rule)
40 }
41}
42
43#[derive(Debug, Default)]
44pub struct Report {
45 pub edits: usize,
47 pub iterations: usize,
49 pub discarded: usize,
52 pub by_rule: BTreeMap<&'static str, usize>,
54}
55
56
57fn legalize_stmt_fold<L: Lang>(
60 lang: &L,
61 snap: &Walk<L>,
62 id: L::Id,
63 edit: Edit<L>,
64) -> Option<Edit<L>> {
65 let Edit::Replace { target, with } = &edit else { return Some(edit) };
66 if *target != id || lang.kind(*with) != NodeKind::Literal {
67 return Some(edit);
68 }
69 if !matches!(lang.kind(id), NodeKind::Call | NodeKind::New) {
72 return Some(edit);
73 }
74 let Some(&(parent, _)) = snap.parents.get(&id) else { return Some(edit) };
75 if lang.kind(parent) != NodeKind::ExprStmt || lang.children(parent).len() != 1 {
76 return Some(edit);
77 }
78 let Some(&(grand, _)) = snap.parents.get(&parent) else { return Some(edit) };
82 if lang.kind(grand) != NodeKind::Block {
83 return None; }
85 Some(Edit::Delete { node: parent })
86}
87
88
89pub fn fold_root<L: Lang>(
98 lang: &mut L,
99 root: L::Id,
100 rules: &[Box<dyn Rule<L>>],
101 cfg: &Config,
102) -> Option<(L::Id, &'static str)> {
103 for rule in rules {
104 if !cfg.enabled(rule.name()) {
105 continue;
106 }
107 let snap = walk(lang, root);
108 let ctx = RewriteCtx { lang: &mut *lang, walk: &snap };
109 if let Some(edit) = rule.check(ctx, root) {
110 if let Edit::Replace { target, with } = edit {
111 if target == root {
112 let red = Edit::Replace { target, with }.cost_reduction(lang);
113 if red > 0 {
114 return Some((with, rule.name()));
115 }
116 }
117 }
118 }
119 }
120 None
121}
122
123pub fn simplify<L: Lang>(
136 lang: &mut L,
137 root: L::Id,
138 rules: &[Box<dyn Rule<L>>],
139 cfg: &Config,
140) -> Report {
141 let mut report = Report::default();
142 lang.prepare(root);
143 let mut dispatch: Vec<Option<Box<[u32]>>> = vec![None; NodeKind::SLOT_COUNT];
147 loop {
148 let mut snap = walk(lang, root);
149 let mut touched: Vec<u64> = Vec::new();
153 let mut moved: Vec<u64> = Vec::new();
157 struct Queued<L: Lang> {
158 #[allow(dead_code)]
161 rule_idx: usize,
162 #[allow(dead_code)]
163 node: L::Id,
164 rule: &'static str,
165 edit: Edit<L>,
166 expects: Vec<Vec<L::Id>>,
167 }
168 let mut queued: Vec<Queued<L>> = Vec::new();
169 let mut applied = false;
170 #[allow(unused_mut)]
171 let applied_immediate = false;
172
173 'scan: for &id in &snap.ids {
174 if bit_get(&touched, lang.node_index(id)) {
175 continue;
176 }
177 if lang.is_opaque(id) {
182 continue;
183 }
184 let kind = lang.kind(id);
185 let ki = kind.slot();
186 debug_assert!(ki < NodeKind::SLOT_COUNT);
187 let bucket: &[u32] = {
188 let slot = &mut dispatch[ki];
189 if slot.is_none() {
190 *slot = Some(
191 rules
192 .iter()
193 .enumerate()
194 .filter(|(_, r)| {
195 let ks = r.kinds();
196 ks.is_empty() || ks.contains(&kind)
197 })
198 .map(|(i, _)| i as u32)
199 .collect::<Vec<u32>>()
200 .into_boxed_slice(),
201 );
202 }
203 slot.as_deref().unwrap()
204 };
205 for &ri in bucket {
206 let rule = &rules[ri as usize];
207 if !cfg.enabled(rule.name()) {
208 continue;
209 }
210 let ctx = RewriteCtx {
211 lang: &mut *lang,
212 walk: &snap,
213 };
214 let proposal = rule.check(ctx, id);
215 let Some(edit) = proposal else {
216 continue;
217 };
218 let Some(edit) = legalize_stmt_fold(&*lang, &snap, id, edit) else {
223 continue;
224 };
225 let structural = rule.structural();
226 let reduction = edit.cost_reduction(lang);
227 debug_assert!(
228 structural || reduction > 0,
229 "rule `{}` proposed a non-simplifying edit",
230 rule.name()
231 );
232 if !structural && reduction == 0 {
233 continue;
234 }
235 if crate::rule::is_replace_only(&edit) {
236 let mut fp = Vec::new();
239 crate::rule::collect_footprint(&*lang, &edit, &mut fp);
240 if fp
241 .iter()
242 .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
243 {
244 continue;
245 }
246 if apply_edit(lang, &snap, &edit).is_ok() {
247 let mut stack = with_roots_of(&edit);
249 while let Some(n) = stack.pop() {
250 if n != snap.root && !snap.parents.contains_key(&n) {
251 bit_set(&mut moved, lang.node_index(n));
252 }
253 for &c in lang.children(n) {
254 stack.push(c);
255 }
256 }
257 if std::env::var("CURE_DEBUG_PASS").is_ok() {
258 eprintln!(
259 "[pass] immediate {} :: {}",
260 rule.name(),
261 describe_edit(&*lang, &edit)
262 );
263 }
264 *report.by_rule.entry(rule.name()).or_insert(0) += 1;
265 report.edits += 1;
266 applied = true;
267 for n in fp {
268 bit_set(&mut touched, lang.node_index(n));
269 }
270 let roots = {
281 let mut r = vec![id];
282 r.extend(edit_invalidation_roots(&edit));
283 r
284 };
285 for root in roots {
286 lang.invalidate_effect(root);
291 let mut cur = Some(root);
292 while let Some(a) =
293 cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
294 {
295 lang.invalidate_effect(a);
296 cur = Some(a);
297 }
298 }
299 continue 'scan;
300 }
301 } else {
302 let mut fp = Vec::new();
305 crate::rule::collect_footprint(&*lang, &edit, &mut fp);
306 if fp
307 .iter()
308 .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
309 {
310 continue;
311 }
312 let mut expects = Vec::new();
313 crate::rule::collect_splice_expectations(&*lang, &edit, &mut expects);
314 queued.push(Queued {
315 rule_idx: ri as usize,
316 node: id,
317 rule: rule.name(),
318 edit,
319 expects,
320 });
321 for n in fp {
322 bit_set(&mut touched, lang.node_index(n));
323 }
324 continue 'scan;
325 }
326 }
327 }
328
329 if !queued.is_empty() {
338 if applied_immediate {
339 snap = walk(lang, root);
340 }
341 queued.sort_by(|a, b| {
342 let ka = crate::rule::structural_sort_key(&snap, &a.edit);
343 let kb = crate::rule::structural_sort_key(&snap, &b.edit);
344 kb.cmp(&ka)
345 });
346 for q in queued {
347 let mut expects = q.expects.as_slice();
348 let ok = crate::rule::verify_deferred(&*lang, &snap, &q.edit, &mut expects);
349 if std::env::var("CURE_DEBUG_PASS").is_ok() {
350 eprintln!(
351 "[pass] deferred {} {} :: {}",
352 if ok { "apply" } else { "DISCARD" },
353 q.rule,
354 describe_edit(&*lang, &q.edit)
355 );
356 }
357 if ok {
358 let roots = edit_invalidation_roots(&q.edit);
362 crate::rule::apply_deferred(&mut *lang, &snap, &q.edit);
363 for root in roots {
364 lang.invalidate_effect(root);
365 let mut cur = Some(root);
366 while let Some(a) =
367 cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
368 {
369 lang.invalidate_effect(a);
370 cur = Some(a);
371 }
372 }
373 *report.by_rule.entry(q.rule).or_insert(0) += 1;
374 report.edits += 1;
375 applied = true;
376 } else {
377 report.discarded += 1;
378 }
379 }
380 }
381
382 report.iterations += 1;
383 if !applied || report.edits >= cfg.max_edits {
384 break;
385 }
386 lang.prepare(root);
387 }
388 report
389}
390
391fn bit_set(bits: &mut Vec<u64>, idx: usize) {
399 if idx >= bits.len() * 64 {
400 bits.resize(idx / 64 + 1, 0);
401 }
402 bits[idx / 64] |= 1u64 << (idx % 64);
403}
404
405fn bit_get(bits: &[u64], idx: usize) -> bool {
407 bits.get(idx / 64)
408 .is_some_and(|w| (w >> (idx % 64)) & 1 == 1)
409}
410
411fn edit_invalidation_roots<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
419 let mut out = Vec::new();
420 collect_all(edit, &mut out);
421 out
422}
423
424fn collect_all<L: Lang>(edit: &Edit<L>, out: &mut Vec<L::Id>) {
425 match edit {
426 Edit::Replace { target, with } => {
427 out.push(*target);
428 out.push(*with);
429 }
430 Edit::Delete { node } => out.push(*node),
431 Edit::Splice { node, insert, .. } => {
432 out.push(*node);
433 out.extend(insert.iter().copied());
434 }
435 Edit::Multi(es) => {
436 for e in es {
437 collect_all(e, out);
438 }
439 }
440 }
441}
442
443fn with_roots_of<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
444 match edit {
445 Edit::Replace { with, .. } => vec![*with],
446 Edit::Multi(es) => es
447 .iter()
448 .filter_map(|e| match e {
449 Edit::Replace { with, .. } => Some(*with),
450 _ => None,
451 })
452 .collect(),
453 _ => Vec::new(),
454 }
455}
456
457fn describe_edit<L: Lang>(lang: &L, edit: &Edit<L>) -> String {
459 fn kind_of<L: Lang>(lang: &L, id: L::Id) -> &'static str {
460 match lang.kind(id) {
461 NodeKind::Block => "Block",
462 NodeKind::VarDecl => "VarDecl",
463 NodeKind::Assign => "Assign",
464 NodeKind::ExprStmt => "ExprStmt",
465 NodeKind::Return => "Return",
466 NodeKind::If => "If",
467 NodeKind::While => "While",
468 NodeKind::VarRef => "VarRef",
469 NodeKind::Literal => "Literal",
470 NodeKind::Binary => "Binary",
471 NodeKind::Call => "Call",
472 NodeKind::Index => "Index",
473 NodeKind::Member => "Member",
474 _ => "…",
475 }
476 }
477 fn go<L: Lang>(lang: &L, e: &Edit<L>, out: &mut String) {
478 match e {
479 Edit::Replace { target, with } => {
480 out.push_str(&format!(
481 "Replace {}→{} ",
482 kind_of(lang, *target),
483 kind_of(lang, *with)
484 ));
485 }
486 Edit::Delete { node } => {
487 out.push_str(&format!("Delete {} ", kind_of(lang, *node)));
488 }
489 Edit::Splice { index, remove, .. } => {
490 out.push_str(&format!("Splice @{index} -{remove} "));
491 }
492 Edit::Multi(es) => {
493 out.push('[');
494 for e in es {
495 go(lang, e, out);
496 }
497 out.push(']');
498 }
499 }
500 }
501 let mut s = String::new();
502 go(lang, edit, &mut s);
503 s
504}