use crate::{
fun::{Book, FanKind, Name, Pattern, Tag, Term},
maybe_grow,
};
use std::collections::HashMap;
impl Book {
pub fn linearize_vars(&mut self) {
for def in self.defs.values_mut() {
def.rule_mut().body.linearize_vars();
}
}
}
impl Term {
pub fn linearize_vars(&mut self) {
term_to_linear(self, &mut HashMap::new());
}
}
fn term_to_linear(term: &mut Term, var_uses: &mut HashMap<Name, u64>) {
maybe_grow(|| match term {
Term::Let { pat: box Pattern::Var(Some(nam)), val, nxt } => {
term_to_linear(nxt, var_uses);
let uses = get_var_uses(Some(nam), var_uses);
term_to_linear(val, var_uses);
match uses {
0 => {
let Term::Let { pat, .. } = term else { unreachable!() };
**pat = Pattern::Var(None);
}
1 => {
nxt.subst(nam, val.as_ref());
*term = std::mem::take(nxt.as_mut());
}
_ => {
let new_pat = duplicate_pat(nam, uses);
let Term::Let { pat, .. } = term else { unreachable!() };
*pat = new_pat;
}
}
}
Term::Var { nam } => {
let instantiated_count = var_uses.entry(nam.clone()).or_default();
*instantiated_count += 1;
*nam = dup_name(nam, *instantiated_count);
}
_ => {
for (child, binds) in term.children_mut_with_binds_mut() {
term_to_linear(child, var_uses);
for bind in binds {
let uses = get_var_uses(bind.as_ref(), var_uses);
match uses {
0 => *bind = None,
1 => (),
uses => duplicate_term(bind.as_ref().unwrap(), child, uses, None),
}
}
}
}
})
}
fn get_var_uses(nam: Option<&Name>, var_uses: &HashMap<Name, u64>) -> u64 {
nam.and_then(|nam| var_uses.get(nam).copied()).unwrap_or_default()
}
fn duplicate_term(nam: &Name, nxt: &mut Term, uses: u64, dup_body: Option<&mut Term>) {
debug_assert!(uses > 1);
*nxt = Term::Let {
pat: duplicate_pat(nam, uses),
val: Box::new(dup_body.map_or_else(|| Term::Var { nam: nam.clone() }, std::mem::take)),
nxt: Box::new(std::mem::take(nxt)),
}
}
fn duplicate_pat(nam: &Name, uses: u64) -> Box<Pattern> {
Box::new(Pattern::Fan(
FanKind::Dup,
Tag::Auto,
(1 .. uses + 1).map(|i| Pattern::Var(Some(dup_name(nam, i)))).collect(),
))
}
fn dup_name(nam: &Name, uses: u64) -> Name {
if uses == 1 { nam.clone() } else { Name::new(format!("{nam}_{uses}")) }
}