use std::{collections::HashMap, rc::Rc};
use crate::ast::{Name, Program, Term, Unique};
#[derive(Eq, Hash, PartialEq, Clone)]
pub struct InternKey {
name: String,
previous_unique: Unique,
}
pub struct CodeGenInterner {
identifiers: HashMap<InternKey, Vec<Unique>>,
current: Unique,
}
impl Default for CodeGenInterner {
fn default() -> Self {
Self::new()
}
}
impl CodeGenInterner {
pub fn new() -> Self {
Self {
identifiers: HashMap::new(),
current: Unique::new(0),
}
}
pub fn program(&mut self, program: &mut Program<Name>) {
self.term(&mut program.term);
}
pub fn term(&mut self, term: &mut Term<Name>) {
match term {
Term::Var(name) => {
let name = Rc::make_mut(name);
name.unique = self.lookup(name.text.clone(), name.unique);
}
Term::Delay(term) => self.term(Rc::make_mut(term)),
Term::Lambda {
parameter_name,
body,
} => {
let parameter_name = Rc::make_mut(parameter_name);
let text = parameter_name.text.clone();
let previous_unique = parameter_name.unique;
parameter_name.unique = self.bind(text.clone(), previous_unique);
self.term(Rc::make_mut(body));
self.unbind(text, previous_unique);
}
Term::Apply { function, argument } => {
self.term(Rc::make_mut(function));
self.term(Rc::make_mut(argument));
}
Term::Constant(_) => (),
Term::Force(term) => self.term(Rc::make_mut(term)),
Term::Error => (),
Term::Builtin(_) => (),
Term::Constr { fields, .. } => {
for field in fields {
self.term(field);
}
}
Term::Case { constr, branches } => {
self.term(Rc::make_mut(constr));
for branch in branches {
self.term(branch);
}
}
}
}
fn bind(&mut self, text: String, previous_unique: Unique) -> Unique {
let key = InternKey {
name: text,
previous_unique,
};
let unique = self.fresh_unique();
self.identifiers.entry(key).or_default().push(unique);
unique
}
fn lookup(&mut self, text: String, previous_unique: Unique) -> Unique {
let key = InternKey {
name: text,
previous_unique,
};
if let Some(u) = self
.identifiers
.get(&key)
.and_then(|uniques| uniques.last())
{
*u
} else {
self.fresh_unique()
}
}
fn unbind(&mut self, text: String, previous_unique: Unique) {
let key = InternKey {
name: text,
previous_unique,
};
let remove_entry = {
let uniques = self
.identifiers
.get_mut(&key)
.expect("missing binder during interning");
uniques.pop().expect("empty binder stack during interning");
uniques.is_empty()
};
if remove_entry {
self.identifiers.remove(&key);
}
}
fn fresh_unique(&mut self) -> Unique {
let unique = self.current;
self.current.increment();
unique
}
}
#[cfg(test)]
mod tests {
use super::CodeGenInterner;
use crate::ast::{Program, Term};
#[test]
fn fresh_shadowing_binders_get_distinct_uniques() {
let mut program = Program {
version: (1, 0, 0),
term: Term::var("x").lambda("x").apply(Term::var("x")).lambda("x"),
};
CodeGenInterner::new().program(&mut program);
let Term::Lambda {
parameter_name: outer,
body,
} = &program.term
else {
panic!("expected outer lambda");
};
let Term::Apply { function, argument } = body.as_ref() else {
panic!("expected application in outer body");
};
let Term::Lambda {
parameter_name: inner,
body,
} = function.as_ref()
else {
panic!("expected inner lambda");
};
let Term::Var(inner_var) = body.as_ref() else {
panic!("expected inner variable");
};
let Term::Var(outer_var) = argument.as_ref() else {
panic!("expected outer variable");
};
assert_ne!(outer.unique, inner.unique);
assert_eq!(inner.unique, inner_var.unique);
assert_eq!(outer.unique, outer_var.unique);
}
}