use std::cell::RefCell;
use std::collections::HashMap;
use super::Node;
#[derive(Clone, Copy, Debug)]
pub struct Ancestors<'tree, 'chain> {
chain: Option<&'chain [Node<'tree>]>,
siblings: Option<&'chain SiblingCache<'tree>>,
folds: Option<&'chain FoldCache>,
}
impl<'tree, 'chain> Ancestors<'tree, 'chain> {
#[inline]
#[must_use]
pub const fn unknown() -> Self {
Self {
chain: None,
siblings: None,
folds: None,
}
}
#[inline]
#[must_use]
pub const fn known(chain: &'chain [Node<'tree>]) -> Self {
Self {
chain: Some(chain),
siblings: None,
folds: None,
}
}
#[inline]
#[must_use]
pub const fn with_siblings(self, cache: &'chain SiblingCache<'tree>) -> Self {
Self {
siblings: Some(cache),
..self
}
}
#[inline]
#[must_use]
pub const fn with_folds(self, cache: &'chain FoldCache) -> Self {
Self {
folds: Some(cache),
..self
}
}
pub(crate) fn fold_down(
self,
node: &Node<'tree>,
skip: usize,
lane: FoldLane,
init: u8,
step: impl Fn(u8, &Node<'tree>, Ancestors<'tree, 'chain>, &Node<'tree>) -> u8,
) -> u8 {
let Some(chain) = self.chain else {
return fold_climbed(node, skip, init, step);
};
let end = chain.len().saturating_sub(skip);
let step_at = |state: u8, depth: usize| {
let outer = Ancestors {
chain: Some(&chain[..depth]),
siblings: self.siblings,
folds: None,
};
step(
state,
&chain[depth],
outer,
chain.get(depth + 1).unwrap_or(node),
)
};
match self.folds {
Some(cache) => cache.fold(lane, chain, end, init, step_at),
None => (0..end).fold(init, step_at),
}
}
pub(crate) fn node_memo(self) -> Option<&'chain NodeMemo> {
self.folds.map(|cache| &cache.nodes)
}
pub(crate) fn memoises_folds(self) -> bool {
self.folds.is_some() && self.chain.is_some()
}
#[must_use]
pub fn checked(chain: &'chain [Node<'tree>], node: &Node<'tree>) -> Self {
#[cfg(chain_audit)]
assert_eq!(
chain.last().map(Node::id),
node.parent().map(|parent| parent.id()),
"ancestor chain desynchronised on a {} node",
node.kind()
);
debug_assert!(
chain.last().is_none_or(|parent| {
parent.id() != node.id()
&& parent.start_byte() <= node.start_byte()
&& node.end_byte() <= parent.end_byte()
}),
"ancestor chain desynchronised on a {} node: chain.last() neither \
contains it nor differs from it",
node.kind()
);
Self::known(chain)
}
#[inline]
pub fn depth(self) -> Option<usize> {
self.chain.map(<[Node<'tree>]>::len)
}
pub(crate) fn children_chain(self, node: &Node<'tree>) -> Option<Vec<Node<'tree>>> {
self.chain
.map(|chain| [chain, std::slice::from_ref(node)].concat())
}
#[inline]
#[must_use]
pub fn parent(self, node: &Node<'tree>) -> Option<Node<'tree>> {
match self.chain {
Some(chain) => chain.last().copied(),
None => node.parent(),
}
}
#[inline]
#[must_use]
pub fn parent_has_kind(self, node: &Node<'tree>, kind: u16) -> bool {
self.parent(node).is_some_and(|p| p.kind_id() == kind)
}
#[inline]
#[must_use]
pub fn previous_sibling(self, node: &Node<'tree>) -> Option<Node<'tree>> {
let Some(chain) = self.chain else {
return node.previous_sibling();
};
let parent = chain.last()?;
if let Lookup::Wide(children, index) = self.lookup(node) {
return match index {
Some(index) => children[..index].last().copied(),
None => node.previous_sibling(),
};
}
let mut previous = None;
for child in parent.children() {
if child.id() == node.id() {
return previous;
}
previous = Some(child);
}
node.previous_sibling()
}
#[inline]
#[must_use]
pub fn iter(self, node: &Node<'tree>) -> AncestorIter<'tree, 'chain> {
match self.chain {
Some(chain) => AncestorIter::Chain(chain, self.siblings),
None => AncestorIter::Climb(node.parent()),
}
}
pub fn siblings_after(
self,
node: &Node<'tree>,
) -> impl Iterator<Item = Node<'tree>> + use<'tree, 'chain> {
let (wide, narrow) = match self.lookup(node) {
Lookup::Wide(children, index) => (index.map(|index| &children[index + 1..]), None),
Lookup::Narrow(parent) => (None, parent.map(|parent| parent.children_after(node))),
};
let wide = wide.into_iter().flatten().copied();
wide.chain(narrow.into_iter().flatten())
}
pub fn siblings_before(
self,
node: &Node<'tree>,
) -> impl Iterator<Item = Node<'tree>> + use<'tree, 'chain> {
let (wide, narrow) = match self.lookup(node) {
Lookup::Wide(children, index) => (index.map(|index| &children[..index]), None),
Lookup::Narrow(parent) => (None, parent.map(|parent| parent.children_before(node))),
};
let wide = wide.into_iter().flatten().rev().copied();
wide.chain(narrow.into_iter().flatten())
}
fn lookup(self, node: &Node<'tree>) -> Lookup<'tree, 'chain> {
let parent = self.parent(node);
let recorded = match (self.siblings, self.chain, parent) {
(Some(cache), Some(chain), Some(parent)) => {
let recorded = cache.children(&parent, chain.len() - 1);
debug_assert!(
recorded.is_some() || parent.child_count() <= WIDE_PARENT,
"a walk lending a SiblingCache must record every wide node on its \
chain (#1584), but not this {}",
parent.kind()
);
recorded
}
_ => None,
};
match recorded {
Some(children) => Lookup::Wide(children, position_by_start(children, node)),
None => Lookup::Narrow(parent),
}
}
}
enum Lookup<'tree, 'chain> {
Wide(&'chain [Node<'tree>], Option<usize>),
Narrow(Option<Node<'tree>>),
}
#[doc(hidden)]
pub const WIDE_PARENT: usize = 64;
fn position_by_start(children: &[Node], node: &Node) -> Option<usize> {
let first = children.partition_point(|child| child.start_byte() < node.start_byte());
children[first..]
.iter()
.take_while(|child| child.start_byte() == node.start_byte())
.position(|child| child.id() == node.id())
.map(|offset| first + offset)
}
#[derive(Debug, Default)]
pub struct SiblingCache<'tree>(Vec<(usize, Vec<Node<'tree>>)>);
impl<'tree> SiblingCache<'tree> {
pub fn record(
&mut self,
node: &Node<'tree>,
depth: usize,
children: impl IntoIterator<Item = Node<'tree>>,
) {
if node.child_count() <= WIDE_PARENT {
return;
}
if self.0.len() <= depth {
self.0.resize_with(depth + 1, Default::default);
}
let (id, recorded) = &mut self.0[depth];
*id = node.id();
recorded.clear();
recorded.extend(children);
}
fn children(&self, parent: &Node<'tree>, depth: usize) -> Option<&[Node<'tree>]> {
self.0
.get(depth)
.filter(|(id, _)| *id == parent.id())
.map(|(_, children)| children.as_slice())
}
}
fn deepest_stored(slots: &[(usize, u8)], chain: &[Node<'_>], from: usize) -> usize {
(1..=from)
.rev()
.find(|&depth| slots[depth].0 == chain[depth].id())
.unwrap_or(0)
}
fn fold_climbed<'tree, 'chain>(
node: &Node<'tree>,
skip: usize,
init: u8,
step: impl Fn(u8, &Node<'tree>, Ancestors<'tree, 'chain>, &Node<'tree>) -> u8,
) -> u8
where
'tree: 'chain,
{
let climbed = climbed_chain(node);
let end = climbed.len().saturating_sub(skip);
(0..end).fold(init, |state, depth| {
let below = climbed.get(depth + 1).unwrap_or(node);
step(state, &climbed[depth], Ancestors::unknown(), below)
})
}
fn climbed_chain<'tree>(node: &Node<'tree>) -> Vec<Node<'tree>> {
let mut climbed: Vec<_> = std::iter::successors(node.parent(), Node::parent).collect();
climbed.reverse();
climbed
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FoldLane {
DocInBody,
DocScope,
BareStringClimb,
}
impl FoldLane {
const COUNT: usize = 3;
}
#[derive(Debug, Default)]
pub struct FoldCache {
lanes: RefCell<[Vec<(usize, u8)>; FoldLane::COUNT]>,
nodes: NodeMemo,
}
#[derive(Debug, Default)]
pub(crate) struct NodeMemo(RefCell<HashMap<usize, bool>>);
impl NodeMemo {
pub(crate) fn get(&self, node: &Node<'_>) -> Option<bool> {
self.0.borrow().get(&node.id()).copied()
}
pub(crate) fn set(&self, node: &Node<'_>, answer: bool) {
self.0.borrow_mut().insert(node.id(), answer);
}
}
impl FoldCache {
fn fold(
&self,
lane: FoldLane,
chain: &[Node<'_>],
end: usize,
init: u8,
step: impl Fn(u8, usize) -> u8,
) -> u8 {
let mut lanes = self.lanes.borrow_mut();
let slots = &mut lanes[lane as usize];
let memo_end = end.min(chain.len().saturating_sub(1));
if slots.len() <= memo_end {
slots.resize(memo_end + 1, (0, 0));
}
let mut depth = deepest_stored(slots, chain, memo_end);
let mut state = if depth == 0 { init } else { slots[depth].1 };
while depth < memo_end {
state = step(state, depth);
depth += 1;
slots[depth] = (chain[depth].id(), state);
}
if end > memo_end {
state = step(state, memo_end);
}
state
}
}
pub enum AncestorIter<'tree, 'chain> {
Chain(&'chain [Node<'tree>], Option<&'chain SiblingCache<'tree>>),
Climb(Option<Node<'tree>>),
}
impl<'tree, 'chain> Iterator for AncestorIter<'tree, 'chain> {
type Item = (Node<'tree>, Ancestors<'tree, 'chain>);
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Chain(remaining, siblings) => {
let (&nearest, above) = remaining.split_last()?;
*remaining = above;
let ancestors = Ancestors {
chain: Some(above),
siblings: *siblings,
folds: None,
};
Some((nearest, ancestors))
}
Self::Climb(next) => {
let nearest = (*next)?;
*next = nearest.parent();
Some((nearest, Ancestors::unknown()))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::Tree;
#[cfg(feature = "perl")]
fn ids<'a>(nodes: impl Iterator<Item = Node<'a>>) -> Vec<usize> {
nodes.map(|node| node.id()).collect()
}
#[cfg(feature = "perl")]
fn raw_children(parent: &Node) -> Vec<usize> {
let raw = parent.as_tree_sitter();
(0..raw.child_count() as u32)
.filter_map(|i| raw.child(i))
.map(|child| child.id())
.collect()
}
#[cfg(feature = "perl")]
fn flat_error() -> Tree {
let source = format!("my @r = ({}", "{\nlast,\n$a && $b,\n".repeat(20));
Tree::new::<crate::langs::PerlCode>(source.as_bytes())
}
#[cfg(feature = "rust")]
#[test]
fn fold_down_agrees_across_its_cache_chain_and_climb_paths() {
let tree = Tree::new::<crate::langs::RustCode>(
b"mod a {\n mod b {\n fn f() { let x = (1 + 2) * 3; }\n }\n fn g() {}\n}\nfn h() { if true { 4 } else { 5 }; }\n",
);
let step = |state: u8, ancestor: &Node, outer: Ancestors, below: &Node| {
let parent = outer
.parent(ancestor)
.map_or(0, |parent| parent.kind_id().to_le_bytes()[0]);
state
.wrapping_mul(31)
.wrapping_add(ancestor.kind_id().to_le_bytes()[0])
.wrapping_add(below.kind_id().to_le_bytes()[0].rotate_left(3))
.wrapping_add(parent.rotate_left(5))
};
let cache = FoldCache::default();
let mut stack = vec![(tree.get_root(), 0_usize)];
let mut chain: Vec<Node> = Vec::new();
let mut queried = 0_usize;
while let Some((node, depth)) = stack.pop() {
chain.truncate(depth);
for skip in 0..=3 {
let cached = |c: &[Node<'_>]| {
Ancestors::known(c).with_folds(&cache).fold_down(
&node,
skip,
FoldLane::DocInBody,
7,
step,
)
};
let first = cached(&chain);
let again = cached(&chain);
let uncached =
Ancestors::known(&chain).fold_down(&node, skip, FoldLane::DocInBody, 7, step);
let climbed =
Ancestors::unknown().fold_down(&node, skip, FoldLane::DocInBody, 7, step);
assert_eq!(
(first, again, uncached),
(climbed, climbed, climbed),
"{} at depth {depth}, skip {skip}",
node.kind()
);
queried += 1;
}
chain.push(node);
let children: Vec<_> = node.children().collect();
stack.extend(children.into_iter().rev().map(|child| (child, depth + 1)));
}
assert!(
queried > 100,
"the fixture must exercise a real walk: {queried}"
);
}
#[cfg(feature = "perl")]
#[test]
fn siblings_of_a_recorded_wide_parent_match_the_child_walk() {
let tree = flat_error();
let root = tree.get_root();
assert!(root.is_error(), "the fixture's tail must fail to parse");
assert!(root.child_count() > WIDE_PARENT, "the ERROR must be wide");
let mut cache = SiblingCache::default();
cache.record(&root, 0, root.children());
let chain = [root];
let ancestors = Ancestors::known(&chain).with_siblings(&cache);
let all = raw_children(&root);
let (mut observed, mut expected) = (Vec::new(), Vec::new());
for (index, child) in root.children().enumerate() {
let recorded_index = match ancestors.lookup(&child) {
Lookup::Wide(_, index) => index,
Lookup::Narrow(_) => None,
};
observed.push((
index,
recorded_index,
ids(ancestors.siblings_after(&child)),
ids(ancestors.siblings_before(&child)),
ancestors.previous_sibling(&child).map(|node| node.id()),
));
expected.push((
index,
Some(index),
all[index + 1..].to_vec(),
all[..index].iter().rev().copied().collect(),
index.checked_sub(1).map(|previous| all[previous]),
));
}
assert_eq!(observed, expected);
let stray = root
.children()
.find_map(|child| {
child
.child(0)
.filter(|g| g.start_byte() == child.start_byte())
})
.expect("the fixture nests a node at its parent's start");
assert!(matches!(ancestors.lookup(&stray), Lookup::Wide(_, None)));
assert_eq!(ancestors.siblings_after(&stray).count(), 0);
assert_eq!(ancestors.siblings_before(&stray).count(), 0);
let operand = root
.children()
.find_map(|child| child.child(2))
.expect("the fixture holds `$a && $b`");
let previous = ancestors
.previous_sibling(&operand)
.expect("the fallback finds the `&&`");
assert_eq!(previous.kind(), "&&");
}
#[cfg(feature = "perl")]
#[test]
fn a_narrow_parent_is_sought_not_recorded() {
let tree = flat_error();
let root = tree.get_root();
let chain = [root];
let narrow = root
.children()
.find(|child| (2..=WIDE_PARENT).contains(&child.child_count()))
.expect("the fixture holds a narrow node with several children");
let mut cache = SiblingCache::default();
cache.record(&narrow, 1, narrow.children());
assert!(cache.0.is_empty(), "a narrow node must not be recorded");
let narrow_chain = [root, narrow];
let all = raw_children(&narrow);
for ancestors in [
Ancestors::known(&narrow_chain).with_siblings(&cache),
Ancestors::known(&narrow_chain),
] {
let first = narrow.child(0).expect("narrow node has children");
assert!(matches!(ancestors.lookup(&first), Lookup::Narrow(Some(_))));
assert_eq!(ids(ancestors.siblings_after(&first)), all[1..]);
}
let first = root.child(0).expect("root has children");
assert!(matches!(
Ancestors::known(&chain).lookup(&first),
Lookup::Narrow(Some(_))
));
}
#[cfg(all(feature = "perl", debug_assertions))]
#[test]
#[should_panic(expected = "must record every wide node on its chain")]
fn an_unrecorded_wide_parent_trips_the_walk_assertion() {
let tree = flat_error();
let root = tree.get_root();
let cache = SiblingCache::default();
let chain = [root];
let first = root.child(0).expect("root has children");
let _ = Ancestors::known(&chain)
.with_siblings(&cache)
.lookup(&first);
}
#[cfg(feature = "kotlin")]
#[test]
fn position_by_start_finds_a_zero_width_child() {
let tree = Tree::new::<crate::langs::KotlinCode>(b"fun f(vararg: Int, b: Int) {}");
let mut zero_width = 0;
for node in tree.get_root().preorder() {
let children: Vec<_> = node.children().collect();
for (index, child) in children.iter().enumerate() {
zero_width += usize::from(child.start_byte() == child.end_byte());
assert_eq!(position_by_start(&children, child), Some(index));
}
}
assert!(
zero_width > 0,
"fixture must hold an inserted zero-width child"
);
}
}