use super::helpers::is_control_flow_block;
use crate::ast::{ElementKind, FragmentNode, Root};
use crate::printer::Printer;
use std::rc::Rc;
#[derive(Clone, Copy, Default, Debug)]
pub(crate) struct EdgeFree {
pub(crate) start: bool,
pub(crate) end: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct IndexedFragment {
len: usize,
ws_ends: (usize, usize),
edges: EdgeFree,
}
pub(crate) struct FragmentGlue {
inline_laid: Box<[bool]>,
glued_before: Box<[bool]>,
glued_after: Box<[bool]>,
edge_back: Box<[bool]>,
edge_fwd: Box<[bool]>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Class {
Transparent,
Space,
Boxless,
Block,
Text { before_ws: bool, after_ws: bool },
Content,
}
const PENDING: usize = 1;
const SEEN: usize = 2;
impl FragmentGlue {
fn step(c: Class, prev: [bool; 4], facing_ws: impl Fn(Class) -> bool) -> [bool; 4] {
let mut out = [false; 4];
for (s, slot) in out.iter_mut().enumerate() {
let pending = s & PENDING != 0;
*slot = match c {
Class::Transparent => prev[s],
Class::Space => prev[s | PENDING],
Class::Boxless => pending || prev[s | SEEN],
Class::Block => true,
Class::Text { .. } => pending || facing_ws(c),
Class::Content => pending,
};
}
out
}
fn edge(free: bool) -> [bool; 4] {
std::array::from_fn(|s| free || s & SEEN == 0)
}
}
impl<'a> Printer<'a> {
fn boxless_class(&self, node: &FragmentNode<'_>) -> Class {
match node {
FragmentNode::Comment(_) => Class::Transparent,
n if n.is_compiler_hoisted() => Class::Transparent,
FragmentNode::Text(t) if t.is_collapsible_ws_only => Class::Space,
FragmentNode::Text(t) => {
let raw = t.raw(self.source);
Class::Text {
before_ws: !Self::text_glued_before(raw),
after_ws: !Self::text_glued_after(raw),
}
}
n if self.is_boxless_raw_text_element(n) => Class::Boxless,
n if self.is_block_element_node(n) => Class::Block,
_ => Class::Content,
}
}
fn compute_fragment_glue(&self, nodes: &[FragmentNode<'_>], edges: EdgeFree) -> FragmentGlue {
let n = nodes.len();
let classes: Vec<Class> = nodes.iter().map(|node| self.boxless_class(node)).collect();
let mut back: Vec<[bool; 4]> = Vec::with_capacity(n + 1);
back.push(FragmentGlue::edge(edges.start));
for g in 1..=n {
let next = FragmentGlue::step(classes[g - 1], back[g - 1], |c| {
matches!(c, Class::Text { after_ws: true, .. })
});
back.push(next);
}
let mut fwd: Vec<[bool; 4]> = vec![[false; 4]; n + 1];
fwd[n] = FragmentGlue::edge(edges.end);
for g in (0..n).rev() {
fwd[g] = FragmentGlue::step(classes[g], fwd[g + 1], |c| {
matches!(
c,
Class::Text {
before_ws: true,
..
}
)
});
}
let gap_free = |g: usize| back[g][0] || fwd[g][0];
let inline_laid: Box<[bool]> = (0..n)
.map(|i| classes[i] == Class::Boxless && !(gap_free(i) && gap_free(i + 1)))
.collect();
let glue_step = |j: usize, next: Option<bool>, facing_ws: bool| -> Option<bool> {
match classes[j] {
Class::Space | Class::Block => Some(false),
Class::Text { .. } => Some(!facing_ws),
Class::Transparent if matches!(nodes[j], FragmentNode::Comment(_)) => Some(true),
Class::Transparent => next,
Class::Boxless if inline_laid[j] => None,
Class::Boxless => Some(false),
Class::Content => Some(true),
}
};
let mut before: Vec<Option<bool>> = Vec::with_capacity(n);
for i in 0..n {
let at = if i == 0 {
Some(false)
} else {
let facing_ws = matches!(classes[i - 1], Class::Text { after_ws: true, .. });
glue_step(i - 1, before[i - 1], facing_ws)
};
before.push(at);
}
let mut after: Vec<Option<bool>> = vec![Some(false); n];
for i in (0..n.saturating_sub(1)).rev() {
let facing_ws = matches!(
classes[i + 1],
Class::Text {
before_ws: true,
..
}
);
after[i] = glue_step(i + 1, after[i + 1], facing_ws);
}
let glued_before: Box<[bool]> = (0..n)
.map(|i| before[i].unwrap_or_else(|| !gap_free(i)))
.collect();
let glued_after: Box<[bool]> = (0..n)
.map(|i| after[i].unwrap_or_else(|| !gap_free(i + 1)))
.collect();
FragmentGlue {
inline_laid,
glued_before,
glued_after,
edge_back: back.iter().map(|b| b[SEEN]).collect(),
edge_fwd: fwd.iter().map(|f| f[SEEN]).collect(),
}
}
fn fragment_glue(&self, nodes: &[FragmentNode<'_>]) -> Rc<FragmentGlue> {
let addr = nodes.as_ptr() as usize;
let key = (addr, nodes.len());
if let Some(glue) = self.fragment_glue_cache.borrow().get(&key) {
return Rc::clone(glue);
}
let size = size_of::<FragmentNode<'_>>();
let index = self.fragment_edges.borrow();
let fragment = index
.range(..=addr)
.next_back()
.map(|(&start, &entry)| ((addr - start) / size, entry))
.filter(|&(offset, entry)| offset + nodes.len() <= entry.len);
debug_assert!(
nodes.is_empty() || index.is_empty() || fragment.is_some(),
"a glue slice lies outside every indexed fragment"
);
debug_assert!(
fragment.is_none_or(|(offset, entry)| {
let (lead, trail) = entry.ws_ends;
(!entry.edges.start && !entry.edges.end)
|| (offset <= lead && entry.len - offset - nodes.len() <= trail)
}),
"a run that stops short of its fragment's ends reads the fragment's edges"
);
let edges = fragment.map_or_else(EdgeFree::default, |(_, entry)| entry.edges);
drop(index);
let glue = Rc::new(self.compute_fragment_glue(nodes, edges));
self.fragment_glue_cache
.borrow_mut()
.insert(key, Rc::clone(&glue));
glue
}
pub(super) fn glued_to_content(
&self,
nodes: &[FragmentNode<'_>],
i: usize,
before: bool,
) -> bool {
let glue = self.fragment_glue(nodes);
if before {
glue.glued_before[i]
} else {
glue.glued_after[i]
}
}
pub(super) fn is_boxless_raw_text_element(&self, node: &FragmentNode<'_>) -> bool {
matches!(node, FragmentNode::Element(el)
if el.facts.is_raw_text() && self.is_block_element(el))
}
pub(super) fn is_glued_raw_text_element(&self, nodes: &[FragmentNode<'_>], i: usize) -> bool {
self.is_boxless_raw_text_element(&nodes[i]) && self.fragment_glue(nodes).inline_laid[i]
}
pub(crate) fn index_fragment_edges(&self, root: &Root<'_>) {
if root.holds_nested_raw_text {
self.index_fragment(root.fragment.nodes, EdgeFree::default());
}
}
fn index_fragment(&self, nodes: &[FragmentNode<'_>], edges: EdgeFree) {
if nodes.is_empty() {
return;
}
let asks = nodes.iter().any(|node| {
is_control_flow_block(node)
|| node.is_declaration()
|| matches!(node, FragmentNode::SpecialElement(se) if se.kind.is_global())
|| self.is_boxless_raw_text_element(node)
});
let ws_lead = nodes
.iter()
.take_while(|n| n.is_whitespace_only_text())
.count();
let ws_trail = nodes
.iter()
.rev()
.take_while(|n| n.is_whitespace_only_text())
.count();
self.fragment_edges.borrow_mut().insert(
nodes.as_ptr() as usize,
IndexedFragment {
len: nodes.len(),
ws_ends: (ws_lead, ws_trail),
edges,
},
);
let glue = asks.then(|| self.fragment_glue(nodes));
let outside = |i: usize| -> EdgeFree {
glue.as_ref().map_or_else(EdgeFree::default, |g| EdgeFree {
start: g.edge_back[i],
end: g.edge_fwd[i + 1],
})
};
let none = EdgeFree::default();
for (i, node) in nodes.iter().enumerate() {
match node {
FragmentNode::Element(el) => {
let facts = el.facts;
let block = el.kind != ElementKind::Component
&& facts.is_block()
&& !facts.is_ws_sensitive();
let e = EdgeFree {
start: block,
end: block,
};
self.index_fragment(el.fragment.nodes, e);
}
FragmentNode::SpecialElement(se) => {
let global = se.kind.is_global();
let e = EdgeFree {
start: global,
end: global,
};
self.index_fragment(se.fragment.nodes, e);
}
FragmentNode::IfBlock(b) => {
let e = outside(i);
self.index_fragment(b.consequent.nodes, e);
if let Some(alt) = &b.alternate {
self.index_fragment(alt.nodes, e);
}
}
FragmentNode::AwaitBlock(b) => {
let e = outside(i);
for f in [&b.pending, &b.then, &b.catch].into_iter().flatten() {
self.index_fragment(f.nodes, e);
}
}
FragmentNode::KeyBlock(b) => self.index_fragment(b.fragment.nodes, outside(i)),
FragmentNode::EachBlock(b) => {
self.index_fragment(b.body.nodes, none);
if let Some(f) = &b.fallback {
self.index_fragment(f.nodes, outside(i));
}
}
FragmentNode::SnippetBlock(b) => self.index_fragment(b.body.nodes, none),
_ => {}
}
}
}
}