use crate::prelude::*;
use super::*;
use crate::util::maybe_grow;
pub(crate) fn calculate_label_sets<'b, 'l>(book: &'b Book, lookup: impl FnMut(&'b str) -> LabSet) -> LabelSets<'b> {
let mut state = State {
book,
lookup,
#[cfg(feature = "std")]
labels: Map::with_capacity(book.len()),
#[cfg(not(feature = "std"))]
labels: Map::new(),
};
for name in book.keys() {
state.visit_def(name, Some(0), None);
}
LabelSets(state.labels)
}
pub(crate) struct LabelSets<'b>(Map<&'b str, LabelState>);
impl<'b> LabelSets<'b> {
pub(crate) fn into_iter(self) -> impl Iterator<Item = (&'b str, LabSet)> {
self.0.into_iter().map(|(nam, lab)| match lab {
LabelState::Done(lab) => (nam, lab),
_ => unreachable!(),
})
}
}
struct State<'b, F> {
book: &'b Book,
lookup: F,
labels: Map<&'b str, LabelState>,
}
#[derive(Debug)]
enum LabelState {
Done(LabSet),
Cycle(usize),
}
impl<'b, F: FnMut(&'b str) -> LabSet> State<'b, F> {
fn visit_def(&mut self, key: &'b str, depth: Option<usize>, out: Option<&mut LabSet>) -> usize {
match self.labels.entry(key) {
Entry::Vacant(e) => {
e.insert(LabelState::Cycle(depth.unwrap()));
self.calc_def(key, depth, out)
}
Entry::Occupied(mut e) => match e.get_mut() {
LabelState::Done(labs) => {
if let Some(out) = out {
out.union(labs);
}
usize::MAX
}
LabelState::Cycle(d) if depth.is_some() => *d,
LabelState::Cycle(_) => {
e.insert(LabelState::Done(LabSet::default()));
self.calc_def(key, depth, out)
}
},
}
}
fn calc_def(&mut self, key: &'b str, depth: Option<usize>, out: Option<&mut LabSet>) -> usize {
let mut labs = LabSet::default();
let head_depth = self.visit_within_def(key, depth, Some(&mut labs));
if let Some(out) = out {
out.union(&labs);
}
if depth.is_some_and(|x| x > head_depth) {
self.labels.insert(key, LabelState::Cycle(head_depth));
} else {
self.labels.insert(key, LabelState::Done(labs));
if depth == Some(head_depth) {
self.visit_within_def(key, None, None);
}
}
head_depth
}
fn visit_within_def(&mut self, key: &str, depth: Option<usize>, mut out: Option<&mut LabSet>) -> usize {
let def = &self.book[key];
let mut head_depth = self.visit_tree(&def.root, depth, out.as_deref_mut());
for (a, b) in &def.redexes {
head_depth = head_depth.min(self.visit_tree(a, depth, out.as_deref_mut()));
head_depth = head_depth.min(self.visit_tree(b, depth, out.as_deref_mut()));
}
head_depth
}
fn visit_tree(&mut self, tree: &'b Tree, depth: Option<usize>, mut out: Option<&mut LabSet>) -> usize {
maybe_grow(move || {
if let Some(lab) = tree.lab() {
if let Some(out) = out.as_deref_mut() {
out.add(lab);
}
}
if let Tree::Ref { nam } = tree {
if self.book.contains_key(nam) {
return self.visit_def(nam, depth.map(|x| x + 1), out);
}
if let Some(out) = &mut out {
out.union(&(self.lookup)(nam));
}
}
tree.children().map(|child| self.visit_tree(child, depth, out.as_deref_mut())).fold(usize::MAX, usize::min)
})
}
}
#[test]
fn test_calculate_labels() {
use alloc::collections::BTreeMap;
assert_eq!(
calculate_label_sets(
&"
@a = {0 @b @c}
@b = {1 @a *}
@c = {2 @a *}
@p = {3 @q {4 x x}}
@q = {5 @r @t}
@r = {6 @s *}
@s = {7 @r *}
@t = {8 @q {9 @t @u}}
@u = {10 @u @s}
"
.parse()
.unwrap(),
|_| unreachable!(),
)
.into_iter()
.collect::<BTreeMap<_, _>>(),
[
("a", [0, 1, 2].into_iter().collect()),
("b", [0, 1, 2].into_iter().collect()),
("c", [0, 1, 2].into_iter().collect()),
("p", [3, 4, 5, 6, 7, 8, 9, 10].into_iter().collect()),
("q", [5, 6, 7, 8, 9, 10].into_iter().collect()),
("r", [6, 7].into_iter().collect()),
("s", [6, 7].into_iter().collect()),
("t", [5, 6, 7, 8, 9, 10].into_iter().collect()),
("u", [6, 7, 10].into_iter().collect()),
]
.into_iter()
.collect()
);
}