use std::collections::{HashMap, HashSet};
use plotnik_bytecode::Nav;
use crate::bytecode::{InstructionIR, Label, MatchIR, NodeTypeIR};
use crate::compile::CompileResult;
const MAX_UP_LEVEL: u8 = 63;
pub fn collapse_up(result: &mut CompileResult) {
let label_to_idx: HashMap<Label, usize> = result
.instructions
.iter()
.enumerate()
.map(|(i, instr)| (instr.label(), i))
.collect();
let mut predecessor_count: HashMap<Label, usize> = HashMap::new();
for instr in &result.instructions {
for succ in instr.successors() {
*predecessor_count.entry(succ).or_default() += 1;
}
}
let mut removed: HashSet<Label> = HashSet::new();
for i in 0..result.instructions.len() {
let InstructionIR::Match(m) = &result.instructions[i] else {
continue;
};
let Some(up_level) = get_up_level(m.nav) else {
continue;
};
if m.successors.len() != 1 {
continue;
}
let mut current_level = up_level;
let mut current_nav = m.nav;
let mut final_successors = m.successors.clone();
while current_level < MAX_UP_LEVEL {
let &[succ_label] = final_successors.as_slice() else {
break;
};
if removed.contains(&succ_label) {
break;
}
let Some(&succ_idx) = label_to_idx.get(&succ_label) else {
break;
};
let InstructionIR::Match(succ) = &result.instructions[succ_idx] else {
break;
};
let Some(succ_level) = get_up_level(succ.nav) else {
break;
};
if !same_up_mode(current_nav, succ.nav) || !is_effectless(succ) {
break;
}
if predecessor_count.get(&succ_label).copied().unwrap_or(0) != 1 {
break;
}
let new_level = current_level.saturating_add(succ_level).min(MAX_UP_LEVEL);
current_nav = set_up_level(current_nav, new_level);
current_level = new_level;
final_successors = succ.successors.clone();
removed.insert(succ_label);
}
if current_level != up_level {
let InstructionIR::Match(m) = &mut result.instructions[i] else {
unreachable!()
};
m.nav = current_nav;
m.successors = final_successors;
}
}
result
.instructions
.retain(|instr| !removed.contains(&instr.label()));
}
fn get_up_level(nav: Nav) -> Option<u8> {
match nav {
Nav::Up(n) | Nav::UpSkipTrivia(n) | Nav::UpExact(n) => Some(n),
_ => None,
}
}
fn set_up_level(nav: Nav, level: u8) -> Nav {
match nav {
Nav::Up(_) => Nav::Up(level),
Nav::UpSkipTrivia(_) => Nav::UpSkipTrivia(level),
Nav::UpExact(_) => Nav::UpExact(level),
_ => nav,
}
}
fn same_up_mode(a: Nav, b: Nav) -> bool {
matches!(
(a, b),
(Nav::Up(_), Nav::Up(_))
| (Nav::UpSkipTrivia(_), Nav::UpSkipTrivia(_))
| (Nav::UpExact(_), Nav::UpExact(_))
)
}
fn is_effectless(m: &MatchIR) -> bool {
m.node_type == NodeTypeIR::Any
&& m.node_field.is_none()
&& m.pre_effects.is_empty()
&& m.neg_fields.is_empty()
&& m.post_effects.is_empty()
&& m.predicate.is_none()
}