use std::sync::Arc;
use crate::canonical::ir::{Bounds, LengthBounds, StringLeaf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StringLeaves {
leaves: Vec<StringLeaf>,
canonical: bool,
}
impl Default for StringLeaves {
fn default() -> Self {
Self {
leaves: Vec::new(),
canonical: true,
}
}
}
impl StringLeaves {
pub(crate) fn insert(&mut self, leaf: StringLeaf) {
self.leaves.push(leaf);
self.canonical = false;
}
fn canonicalize(&mut self) {
if self.canonical {
return;
}
let was_empty = self.leaves.is_empty();
self.leaves = merge(std::mem::take(&mut self.leaves));
drop_subsumed(&mut self.leaves);
self.canonical = true;
debug_assert_eq!(
self.leaves.is_empty(),
was_empty,
"merging emptied the leaves"
);
}
pub(crate) fn clear(&mut self) {
self.leaves.clear();
self.canonical = true;
}
pub(crate) fn retain(&mut self, keep: impl FnMut(&StringLeaf) -> bool) {
self.canonicalize();
self.leaves.retain(keep);
}
pub(crate) fn is_empty(&self) -> bool {
self.leaves.is_empty()
}
pub(crate) fn as_slice(&mut self) -> &[StringLeaf] {
self.canonicalize();
&self.leaves
}
}
impl IntoIterator for StringLeaves {
type Item = StringLeaf;
type IntoIter = std::vec::IntoIter<StringLeaf>;
fn into_iter(mut self) -> Self::IntoIter {
self.canonicalize();
self.leaves.into_iter()
}
}
struct Facets {
patterns: Vec<Arc<str>>,
formats: Vec<Arc<str>>,
content_media_types: Vec<Arc<str>>,
content_encodings: Vec<Arc<str>>,
}
fn merge(mut leaves: Vec<StringLeaf>) -> Vec<StringLeaf> {
if leaves.len() < 2 {
return leaves;
}
leaves.sort_by(|left, right| {
(
&left.patterns,
&left.formats,
&left.content_media_types,
&left.content_encodings,
)
.cmp(&(
&right.patterns,
&right.formats,
&right.content_media_types,
&right.content_encodings,
))
});
let mut merged: Vec<StringLeaf> = Vec::with_capacity(leaves.len());
let mut windows: Vec<LengthBounds> = Vec::new();
let mut facets: Option<Facets> = None;
for leaf in leaves {
if facets.as_ref().is_none_or(|group| {
group.patterns != leaf.patterns
|| group.formats != leaf.formats
|| group.content_media_types != leaf.content_media_types
|| group.content_encodings != leaf.content_encodings
}) {
flush_group(&mut merged, facets.take(), &mut windows);
facets = Some(Facets {
patterns: leaf.patterns,
formats: leaf.formats,
content_media_types: leaf.content_media_types,
content_encodings: leaf.content_encodings,
});
}
windows.push(leaf.lengths);
}
flush_group(&mut merged, facets, &mut windows);
merged
}
fn flush_group(
merged: &mut Vec<StringLeaf>,
facets: Option<Facets>,
windows: &mut Vec<LengthBounds>,
) {
let Some(Facets {
patterns,
formats,
content_media_types,
content_encodings,
}) = facets
else {
return;
};
let mut lengths = Bounds::merge_all(std::mem::take(windows));
let last = lengths.pop().expect("a group holds at least one window");
for window in lengths {
merged.push(StringLeaf {
lengths: window,
patterns: patterns.clone(),
formats: formats.clone(),
content_media_types: content_media_types.clone(),
content_encodings: content_encodings.clone(),
});
}
merged.push(StringLeaf {
lengths: last,
patterns,
formats,
content_media_types,
content_encodings,
});
}
fn drop_subsumed(leaves: &mut Vec<StringLeaf>) {
if leaves.len() < 2 {
return;
}
let mut keep = vec![true; leaves.len()];
for (index, leaf) in leaves.iter().enumerate() {
for (other_index, other) in leaves.iter().enumerate() {
if index == other_index || !keep[other_index] || !keep[index] {
continue;
}
let wider = other.lengths.covers(&leaf.lengths)
&& other
.patterns
.iter()
.all(|pattern| leaf.patterns.contains(pattern))
&& other
.formats
.iter()
.all(|format| leaf.formats.contains(format))
&& other
.content_media_types
.iter()
.all(|media_type| leaf.content_media_types.contains(media_type))
&& other
.content_encodings
.iter()
.all(|encoding| leaf.content_encodings.contains(encoding));
let facets = |leaf: &StringLeaf| {
leaf.patterns.len()
+ leaf.formats.len()
+ leaf.content_media_types.len()
+ leaf.content_encodings.len()
};
if wider && (facets(other) < facets(leaf) || index > other_index) {
keep[index] = false;
}
}
}
let mut index = 0;
leaves.retain(|_| {
let keeps = keep[index];
index += 1;
keeps
});
}