use std::collections::{BTreeMap, HashSet};
use crate::kind::NodeKind;
use crate::rule::{apply_edit, Edit, RewriteCtx, Rule};
use crate::walk::{walk, Walk};
use crate::lang::Lang;
#[derive(Clone, Debug)]
pub struct Config {
pub max_edits: usize,
pub disabled_rules: HashSet<String>,
pub remove_dead_methods: bool,
}
impl Default for Config {
fn default() -> Self {
Config {
max_edits: 100_000,
disabled_rules: HashSet::new(),
remove_dead_methods: false,
}
}
}
impl Config {
pub fn enabled(&self, rule: &str) -> bool {
!self.disabled_rules.contains(rule)
}
}
#[derive(Debug, Default)]
pub struct Report {
pub edits: usize,
pub iterations: usize,
pub discarded: usize,
pub by_rule: BTreeMap<&'static str, usize>,
}
fn legalize_stmt_fold<L: Lang>(
lang: &L,
snap: &Walk<L>,
id: L::Id,
edit: Edit<L>,
) -> Option<Edit<L>> {
let Edit::Replace { target, with } = &edit else { return Some(edit) };
if *target != id || lang.kind(*with) != NodeKind::Literal {
return Some(edit);
}
if !matches!(lang.kind(id), NodeKind::Call | NodeKind::New) {
return Some(edit);
}
let Some(&(parent, _)) = snap.parents.get(&id) else { return Some(edit) };
if lang.kind(parent) != NodeKind::ExprStmt || lang.children(parent).len() != 1 {
return Some(edit);
}
let Some(&(grand, _)) = snap.parents.get(&parent) else { return Some(edit) };
if lang.kind(grand) != NodeKind::Block {
return None; }
Some(Edit::Delete { node: parent })
}
pub fn fold_root<L: Lang>(
lang: &mut L,
root: L::Id,
rules: &[Box<dyn Rule<L>>],
cfg: &Config,
) -> Option<(L::Id, &'static str)> {
for rule in rules {
if !cfg.enabled(rule.name()) {
continue;
}
let snap = walk(lang, root);
let ctx = RewriteCtx { lang: &mut *lang, walk: &snap };
if let Some(edit) = rule.check(ctx, root) {
if let Edit::Replace { target, with } = edit {
if target == root {
let red = Edit::Replace { target, with }.cost_reduction(lang);
if red > 0 {
return Some((with, rule.name()));
}
}
}
}
}
None
}
pub fn simplify<L: Lang>(
lang: &mut L,
root: L::Id,
rules: &[Box<dyn Rule<L>>],
cfg: &Config,
) -> Report {
let mut report = Report::default();
lang.prepare(root);
let mut dispatch: Vec<Option<Box<[u32]>>> = vec![None; NodeKind::SLOT_COUNT];
loop {
let mut snap = walk(lang, root);
let mut touched: Vec<u64> = Vec::new();
let mut moved: Vec<u64> = Vec::new();
struct Queued<L: Lang> {
#[allow(dead_code)]
rule_idx: usize,
#[allow(dead_code)]
node: L::Id,
rule: &'static str,
edit: Edit<L>,
expects: Vec<Vec<L::Id>>,
}
let mut queued: Vec<Queued<L>> = Vec::new();
let mut applied = false;
#[allow(unused_mut)]
let applied_immediate = false;
'scan: for &id in &snap.ids {
if bit_get(&touched, lang.node_index(id)) {
continue;
}
let kind = lang.kind(id);
let ki = kind.slot();
debug_assert!(ki < NodeKind::SLOT_COUNT);
let bucket: &[u32] = {
let slot = &mut dispatch[ki];
if slot.is_none() {
*slot = Some(
rules
.iter()
.enumerate()
.filter(|(_, r)| {
let ks = r.kinds();
ks.is_empty() || ks.contains(&kind)
})
.map(|(i, _)| i as u32)
.collect::<Vec<u32>>()
.into_boxed_slice(),
);
}
slot.as_deref().unwrap()
};
for &ri in bucket {
let rule = &rules[ri as usize];
if !cfg.enabled(rule.name()) {
continue;
}
let ctx = RewriteCtx {
lang: &mut *lang,
walk: &snap,
};
let proposal = rule.check(ctx, id);
let Some(edit) = proposal else {
continue;
};
let Some(edit) = legalize_stmt_fold(&*lang, &snap, id, edit) else {
continue;
};
let structural = rule.structural();
let reduction = edit.cost_reduction(lang);
debug_assert!(
structural || reduction > 0,
"rule `{}` proposed a non-simplifying edit",
rule.name()
);
if !structural && reduction == 0 {
continue;
}
if crate::rule::is_replace_only(&edit) {
let mut fp = Vec::new();
crate::rule::collect_footprint(&*lang, &edit, &mut fp);
if fp
.iter()
.any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
{
continue;
}
if apply_edit(lang, &snap, &edit).is_ok() {
let mut stack = with_roots_of(&edit);
while let Some(n) = stack.pop() {
if n != snap.root && !snap.parents.contains_key(&n) {
bit_set(&mut moved, lang.node_index(n));
}
for &c in lang.children(n) {
stack.push(c);
}
}
if std::env::var("CURE_DEBUG_PASS").is_ok() {
eprintln!(
"[pass] immediate {} :: {}",
rule.name(),
describe_edit(&*lang, &edit)
);
}
*report.by_rule.entry(rule.name()).or_insert(0) += 1;
report.edits += 1;
applied = true;
for n in fp {
bit_set(&mut touched, lang.node_index(n));
}
let roots = {
let mut r = vec![id];
r.extend(edit_invalidation_roots(&edit));
r
};
for root in roots {
lang.invalidate_effect(root);
let mut cur = Some(root);
while let Some(a) =
cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
{
lang.invalidate_effect(a);
cur = Some(a);
}
}
continue 'scan;
}
} else {
let mut fp = Vec::new();
crate::rule::collect_footprint(&*lang, &edit, &mut fp);
if fp
.iter()
.any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
{
continue;
}
let mut expects = Vec::new();
crate::rule::collect_splice_expectations(&*lang, &edit, &mut expects);
queued.push(Queued {
rule_idx: ri as usize,
node: id,
rule: rule.name(),
edit,
expects,
});
for n in fp {
bit_set(&mut touched, lang.node_index(n));
}
continue 'scan;
}
}
}
if !queued.is_empty() {
if applied_immediate {
snap = walk(lang, root);
}
queued.sort_by(|a, b| {
let ka = crate::rule::structural_sort_key(&snap, &a.edit);
let kb = crate::rule::structural_sort_key(&snap, &b.edit);
kb.cmp(&ka)
});
for q in queued {
let mut expects = q.expects.as_slice();
let ok = crate::rule::verify_deferred(&*lang, &snap, &q.edit, &mut expects);
if std::env::var("CURE_DEBUG_PASS").is_ok() {
eprintln!(
"[pass] deferred {} {} :: {}",
if ok { "apply" } else { "DISCARD" },
q.rule,
describe_edit(&*lang, &q.edit)
);
}
if ok {
let roots = edit_invalidation_roots(&q.edit);
crate::rule::apply_deferred(&mut *lang, &snap, &q.edit);
for root in roots {
lang.invalidate_effect(root);
let mut cur = Some(root);
while let Some(a) =
cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
{
lang.invalidate_effect(a);
cur = Some(a);
}
}
*report.by_rule.entry(q.rule).or_insert(0) += 1;
report.edits += 1;
applied = true;
} else {
report.discarded += 1;
}
}
}
report.iterations += 1;
if !applied || report.edits >= cfg.max_edits {
break;
}
lang.prepare(root);
}
report
}
fn bit_set(bits: &mut Vec<u64>, idx: usize) {
if idx >= bits.len() * 64 {
bits.resize(idx / 64 + 1, 0);
}
bits[idx / 64] |= 1u64 << (idx % 64);
}
fn bit_get(bits: &[u64], idx: usize) -> bool {
bits.get(idx / 64)
.is_some_and(|w| (w >> (idx % 64)) & 1 == 1)
}
fn edit_invalidation_roots<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
let mut out = Vec::new();
collect_all(edit, &mut out);
out
}
fn collect_all<L: Lang>(edit: &Edit<L>, out: &mut Vec<L::Id>) {
match edit {
Edit::Replace { target, with } => {
out.push(*target);
out.push(*with);
}
Edit::Delete { node } => out.push(*node),
Edit::Splice { node, insert, .. } => {
out.push(*node);
out.extend(insert.iter().copied());
}
Edit::Multi(es) => {
for e in es {
collect_all(e, out);
}
}
}
}
fn with_roots_of<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
match edit {
Edit::Replace { with, .. } => vec![*with],
Edit::Multi(es) => es
.iter()
.filter_map(|e| match e {
Edit::Replace { with, .. } => Some(*with),
_ => None,
})
.collect(),
_ => Vec::new(),
}
}
fn describe_edit<L: Lang>(lang: &L, edit: &Edit<L>) -> String {
fn kind_of<L: Lang>(lang: &L, id: L::Id) -> &'static str {
match lang.kind(id) {
NodeKind::Block => "Block",
NodeKind::VarDecl => "VarDecl",
NodeKind::Assign => "Assign",
NodeKind::ExprStmt => "ExprStmt",
NodeKind::Return => "Return",
NodeKind::If => "If",
NodeKind::While => "While",
NodeKind::VarRef => "VarRef",
NodeKind::Literal => "Literal",
NodeKind::Binary => "Binary",
NodeKind::Call => "Call",
NodeKind::Index => "Index",
NodeKind::Member => "Member",
_ => "…",
}
}
fn go<L: Lang>(lang: &L, e: &Edit<L>, out: &mut String) {
match e {
Edit::Replace { target, with } => {
out.push_str(&format!(
"Replace {}→{} ",
kind_of(lang, *target),
kind_of(lang, *with)
));
}
Edit::Delete { node } => {
out.push_str(&format!("Delete {} ", kind_of(lang, *node)));
}
Edit::Splice { index, remove, .. } => {
out.push_str(&format!("Splice @{index} -{remove} "));
}
Edit::Multi(es) => {
out.push('[');
for e in es {
go(lang, e, out);
}
out.push(']');
}
}
}
let mut s = String::new();
go(lang, edit, &mut s);
s
}