use std::collections::{BTreeMap, HashSet};
use std::hash::BuildHasher;
use oneyaml_syntax::tree::{Node as TreeNode, SyntaxKind, SyntaxTree};
type TreeId = oneyaml_syntax::tree::NodeId;
#[derive(Debug, Default)]
pub(super) struct Names<'t> {
pub(super) anchors: Vec<(&'t str, TreeId)>,
pub(super) aliases: Vec<(&'t str, TreeId)>,
}
impl<'t> Names<'t> {
pub(super) fn of(first: TreeNode<'t>, last: TreeNode<'t>) -> Self {
let mut out = Self::default();
Self::visit(first, last, |alias, name, id| {
if alias {
out.aliases.push((name, id));
} else {
out.anchors.push((name, id));
}
});
out
}
fn visit(first: TreeNode<'t>, last: TreeNode<'t>, mut f: impl FnMut(bool, &'t str, TreeId)) {
let mut at = Some(first);
while let Some(s) = at {
for n in s.descendants() {
match n.kind() {
SyntaxKind::Anchor if n != s => {
let Some(p) = n.parent() else { continue };
let Some(a) = p.anchor() else { continue };
if a.span.start == n.span().start {
f(false, a.name, p.id());
}
}
SyntaxKind::Alias => {
if let Some(a) = n.alias_name() {
f(true, a, n.id());
}
}
_ => {}
}
}
if s == last {
break;
}
at = s.next_sibling();
}
}
pub(super) fn is_empty(&self) -> bool {
self.anchors.is_empty() && self.aliases.is_empty()
}
}
const BIG: usize = 256;
const SMALL: usize = 64;
const CHUNK: usize = 64;
const CAP: usize = 128;
const MIN: usize = 32;
const NIL: u32 = u32::MAX;
#[derive(Clone, Debug)]
struct Chunk {
ids: Vec<TreeId>,
left: u32,
right: u32,
prio: u32,
size: usize,
}
#[derive(Clone, Debug)]
struct Chunks {
nodes: Vec<Chunk>,
free: Vec<u32>,
root: u32,
rng: u64,
broken: bool,
}
impl Chunks {
fn build(ids: &[TreeId]) -> Self {
let mut out = Self::empty();
for id in ids {
out.push_back(*id);
}
out.link();
out
}
fn empty() -> Self {
let seed = std::collections::hash_map::RandomState::new().hash_one(0u8);
Self {
nodes: Vec::new(),
free: Vec::new(),
root: NIL,
rng: seed | 1,
broken: false,
}
}
fn push_back(&mut self, id: TreeId) {
match self.nodes.last_mut() {
Some(c) if c.ids.len() < CAP => c.ids.push(id),
_ => {
let mut ids = Vec::with_capacity(CAP);
ids.push(id);
if self.fresh(ids).is_none() {
self.broken = true;
}
}
}
}
fn link(&mut self) {
let mut stack: Vec<u32> = Vec::new();
let count = u32::try_from(self.nodes.len()).unwrap_or(NIL);
for n in 0..count {
let prio = self.prio(n);
let mut last = NIL;
while let Some(&t) = stack.last() {
if self.prio(t) < prio {
last = t;
stack.pop();
} else {
break;
}
}
if let Some(c) = self.at_mut(n) {
c.left = last;
c.right = NIL;
}
if let Some(&t) = stack.last() {
if let Some(c) = self.at_mut(t) {
c.right = n;
}
}
stack.push(n);
}
self.root = stack.first().copied().unwrap_or(NIL);
let mut order = Vec::with_capacity(self.nodes.len());
let mut todo = vec![self.root];
while let Some(n) = todo.pop() {
if let Some(c) = self.at(n) {
order.push(n);
todo.push(c.left);
todo.push(c.right);
}
}
for n in order.into_iter().rev() {
self.fix(n);
}
}
fn at(&self, n: u32) -> Option<&Chunk> {
self.nodes.get(usize::try_from(n).ok()?)
}
fn at_mut(&mut self, n: u32) -> Option<&mut Chunk> {
self.nodes.get_mut(usize::try_from(n).ok()?)
}
fn size(&self, n: u32) -> usize {
self.at(n).map_or(0, |c| c.size)
}
fn prio(&self, n: u32) -> u32 {
self.at(n).map_or(0, |c| c.prio)
}
fn len(&self) -> usize {
self.size(self.root)
}
fn fix(&mut self, n: u32) {
let Some(c) = self.at(n) else { return };
let (l, r, own) = (c.left, c.right, c.ids.len());
let size = self
.size(l)
.saturating_add(self.size(r))
.saturating_add(own);
if let Some(c) = self.at_mut(n) {
c.size = size;
}
}
fn fresh(&mut self, ids: Vec<TreeId>) -> Option<u32> {
let mut x = self.rng;
x ^= x.wrapping_shl(13);
x ^= x.wrapping_shr(7);
x ^= x.wrapping_shl(17);
self.rng = x;
let chunk = Chunk {
size: ids.len(),
ids,
left: NIL,
right: NIL,
prio: x.wrapping_shr(32) as u32,
};
if let Some(n) = self.free.pop() {
if let Some(c) = self.at_mut(n) {
*c = chunk;
return Some(n);
}
return None;
}
let n = u32::try_from(self.nodes.len()).ok().filter(|n| *n != NIL)?;
self.nodes.push(chunk);
Some(n)
}
fn find(&self, mut r: usize) -> Option<(Vec<u32>, usize)> {
let mut path = Vec::new();
let mut cur = self.root;
while let Some(c) = self.at(cur) {
path.push(cur);
let ls = self.size(c.left);
if r < ls {
cur = c.left;
continue;
}
let r2 = r.saturating_sub(ls);
if r2 < c.ids.len() {
return Some((path, r2));
}
r = r2.saturating_sub(c.ids.len());
cur = c.right;
}
None
}
fn resize(&mut self, path: &[u32], delta: usize, sub: bool) {
for &n in path {
if let Some(c) = self.at_mut(n) {
c.size = if sub {
c.size.saturating_sub(delta)
} else {
c.size.saturating_add(delta)
};
}
}
}
fn relink(&mut self, parent: Option<u32>, old: u32, new: u32) {
match parent.and_then(|p| self.at_mut(p)) {
Some(p) if p.left == old => p.left = new,
Some(p) => p.right = new,
None => self.root = new,
}
}
fn rotate(&mut self, p: u32, x: u32) {
let left = self.at(p).is_some_and(|c| c.left == x);
if left {
let b = self.at(x).map_or(NIL, |c| c.right);
if let Some(c) = self.at_mut(p) {
c.left = b;
}
if let Some(c) = self.at_mut(x) {
c.right = p;
}
} else {
let b = self.at(x).map_or(NIL, |c| c.left);
if let Some(c) = self.at_mut(p) {
c.right = b;
}
if let Some(c) = self.at_mut(x) {
c.left = p;
}
}
self.fix(p);
self.fix(x);
}
fn insert_chunk(&mut self, mut r: usize, ids: Vec<TreeId>) {
let len = ids.len();
let Some(n) = self.fresh(ids) else {
self.broken = true;
return;
};
let mut path = Vec::new();
let mut cur = self.root;
let mut went_left = false;
while let Some(c) = self.at(cur) {
path.push(cur);
let (l, rt, ls, own) = (c.left, c.right, self.size(c.left), c.ids.len());
if r <= ls {
went_left = true;
cur = l;
} else {
if r < ls.saturating_add(own) {
self.broken = true;
}
r = r.saturating_sub(ls.saturating_add(own));
went_left = false;
cur = rt;
}
}
self.resize(&path, len, false);
match path.last().copied().and_then(|p| self.at_mut(p)) {
Some(p) if went_left => p.left = n,
Some(p) => p.right = n,
None => self.root = n,
}
let prio = self.prio(n);
while let Some(&p) = path.last() {
if self.prio(p) >= prio {
break;
}
path.pop();
self.rotate(p, n);
self.relink(path.last().copied(), p, n);
}
}
fn delete(&mut self, mut path: Vec<u32>) -> Vec<TreeId> {
let Some(&x) = path.last() else {
return Vec::new();
};
let ids = self
.at_mut(x)
.map(|c| std::mem::take(&mut c.ids))
.unwrap_or_default();
self.resize(&path, ids.len(), true);
while let Some(c) = self.at(x) {
let (l, r) = (c.left, c.right);
let up = match (l == NIL, r == NIL) {
(true, true) => break,
(true, false) => r,
(false, true) => l,
(false, false) if self.prio(l) > self.prio(r) => l,
(false, false) => r,
};
let parent = path.len().checked_sub(2).and_then(|i| path.get(i)).copied();
self.rotate(x, up);
self.relink(parent, x, up);
let at = path.len().saturating_sub(1);
path.insert(at, up);
}
path.pop();
self.relink(path.last().copied(), x, NIL);
self.free.push(x);
ids
}
fn lower_bound(&self, tree: &SyntaxTree, p: usize) -> usize {
let mut acc = 0usize;
let mut cur = self.root;
while let Some(c) = self.at(cur) {
let (Some(first), Some(last)) = (c.ids.first(), c.ids.last()) else {
debug_assert!(false, "an empty chunk");
return acc;
};
let ls = self.size(c.left);
if pos(tree, *last) < p {
acc = acc.saturating_add(ls).saturating_add(c.ids.len());
cur = c.right;
} else if pos(tree, *first) >= p {
cur = c.left;
} else {
let i = c.ids.partition_point(|x| pos(tree, *x) < p);
return acc.saturating_add(ls).saturating_add(i);
}
}
acc
}
fn get(&self, mut r: usize) -> Option<TreeId> {
let mut cur = self.root;
while let Some(c) = self.at(cur) {
let ls = self.size(c.left);
if r < ls {
cur = c.left;
continue;
}
let r2 = r.saturating_sub(ls);
if r2 < c.ids.len() {
return c.ids.get(r2).copied();
}
r = r2.saturating_sub(c.ids.len());
cur = c.right;
}
None
}
fn collect(&self, a: usize, b: usize) -> Vec<TreeId> {
let need = b.saturating_sub(a);
let mut out = Vec::with_capacity(need.min(self.len()));
if need == 0 {
return out;
}
let mut pending = Vec::new();
let mut cur = self.root;
let mut r = a;
let mut start = None;
while let Some(c) = self.at(cur) {
let ls = self.size(c.left);
if r < ls {
pending.push(cur);
cur = c.left;
continue;
}
let r2 = r.saturating_sub(ls);
if r2 < c.ids.len() {
start = Some((cur, r2));
break;
}
r = r2.saturating_sub(c.ids.len());
cur = c.right;
}
let Some((mut node, mut off)) = start else {
return out;
};
while let Some(c) = self.at(node) {
let take = need.saturating_sub(out.len());
let end = off.saturating_add(take).min(c.ids.len());
out.extend_from_slice(c.ids.get(off..end).unwrap_or_default());
if out.len() >= need {
break;
}
let mut next = c.right;
while let Some(d) = self.at(next) {
pending.push(next);
next = d.left;
}
match pending.pop() {
Some(n) => (node, off) = (n, 0),
None => break,
}
}
out
}
fn insert_run(&mut self, at: usize, run: &[TreeId]) {
if run.is_empty() {
return;
}
let target = match at.checked_sub(1) {
Some(before) => self.find(before).map(|(p, o)| (p, o.saturating_add(1))),
None => self.find(0),
};
let Some((path, off)) = target else {
for piece in run.chunks(CHUNK) {
let r = self.len();
self.insert_chunk(r, piece.to_vec());
}
return;
};
let Some(&x) = path.last() else { return };
let old = self.at(x).map_or(0, |c| c.ids.len());
if old.saturating_add(run.len()) <= CAP {
if let Some(c) = self.at_mut(x) {
let off = off.min(c.ids.len());
c.ids.splice(off..off, run.iter().copied());
}
self.resize(&path, run.len(), false);
return;
}
let start = at.saturating_sub(off);
let mut all = self
.at_mut(x)
.map(|c| std::mem::take(&mut c.ids))
.unwrap_or_default();
let tail = all.split_off(off.min(all.len()));
all.extend_from_slice(run);
all.extend(tail);
let total = all.len();
let m = total.div_ceil(CHUNK).max(1);
let (base, extra) = (
total.checked_div(m).unwrap_or(0),
total.checked_rem(m).unwrap_or(0),
);
let mut pieces = Vec::with_capacity(m);
let mut from = 0usize;
for i in 0..m {
let n = if i < extra {
base.saturating_add(1)
} else {
base
};
let to = from.saturating_add(n).min(total);
pieces.push(all.get(from..to).unwrap_or_default().to_vec());
from = to;
}
let mut pieces = pieces.into_iter();
let first = pieces.next().unwrap_or_default();
let first_len = first.len();
if let Some(c) = self.at_mut(x) {
c.ids = first;
}
self.resize(&path, old, true);
self.resize(&path, first_len, false);
let mut r = start.saturating_add(first_len);
for p in pieces {
let len = p.len();
self.insert_chunk(r, p);
r = r.saturating_add(len);
}
}
fn remove_range(&mut self, a: usize, b: usize) {
let mut left = b.saturating_sub(a);
while left > 0 {
let Some((path, off)) = self.find(a) else {
self.broken = true;
break;
};
let Some(&x) = path.last() else { break };
let len = self.at(x).map_or(0, |c| c.ids.len());
let take = left.min(len.saturating_sub(off));
if take == 0 {
self.broken = true;
break;
}
if off == 0 && take == len {
self.delete(path);
} else {
if let Some(c) = self.at_mut(x) {
c.ids.drain(off..off.saturating_add(take));
if c.ids.capacity() > c.ids.len().saturating_mul(2).max(CHUNK) {
c.ids.shrink_to_fit();
}
}
self.resize(&path, take, true);
}
left = left.saturating_sub(take);
}
self.settle(a);
if let Some(before) = a.checked_sub(1) {
self.settle(before);
}
}
fn settle(&mut self, r: usize) {
let Some((path, off)) = self.find(r) else {
return;
};
let Some(&x) = path.last() else { return };
let len = self.at(x).map_or(0, |c| c.ids.len());
if len >= MIN {
return;
}
let start = r.saturating_sub(off);
let end = start.saturating_add(len);
if let Some((spath, _)) = self.find(end) {
let slen = spath
.last()
.and_then(|s| self.at(*s))
.map_or(0, |c| c.ids.len());
if len.saturating_add(slen) <= CAP {
let ids = self.delete(spath);
if let Some((xpath, _)) = self.find(start) {
if let Some(c) = xpath.last().and_then(|x| self.at_mut(*x)) {
c.ids.extend_from_slice(&ids);
}
self.resize(&xpath, ids.len(), false);
}
return;
}
}
if let Some(prev) = start.checked_sub(1) {
if let Some((ppath, _)) = self.find(prev) {
let plen = ppath
.last()
.and_then(|p| self.at(*p))
.map_or(0, |c| c.ids.len());
if plen.saturating_add(len) <= CAP {
let ids = self.delete(path);
if let Some((ppath, _)) = self.find(prev) {
if let Some(c) = ppath.last().and_then(|p| self.at_mut(*p)) {
c.ids.extend_from_slice(&ids);
}
self.resize(&ppath, ids.len(), false);
}
}
}
}
}
}
#[derive(Clone, Debug)]
enum List {
Small(Vec<TreeId>),
Big(Box<Chunks>),
}
impl Default for List {
fn default() -> Self {
List::Small(Vec::new())
}
}
impl List {
fn push_built(&mut self, id: TreeId) {
match self {
List::Small(v) if v.len() < CAP => v.push(id),
List::Small(v) => {
let mut c = Chunks::empty();
let first = std::mem::take(v);
if c.fresh(first).is_none() {
c.broken = true;
}
c.push_back(id);
*self = List::Big(Box::new(c));
}
List::Big(c) => c.push_back(id),
}
}
fn seal(&mut self) {
if let List::Big(c) = self {
c.link();
}
}
fn from_vec(ids: Vec<TreeId>) -> Self {
if ids.len() > BIG {
List::Big(Box::new(Chunks::build(&ids)))
} else {
List::Small(ids)
}
}
fn len(&self) -> usize {
match self {
List::Small(v) => v.len(),
List::Big(c) => c.len(),
}
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn broken(&self) -> bool {
matches!(self, List::Big(c) if c.broken)
}
fn lower_bound(&self, tree: &SyntaxTree, p: usize) -> usize {
match self {
List::Small(v) => v.partition_point(|x| pos(tree, *x) < p),
List::Big(c) => c.lower_bound(tree, p),
}
}
fn collect(&self, a: usize, b: usize) -> Vec<TreeId> {
match self {
List::Small(v) => v.get(a..b.max(a)).unwrap_or_default().to_vec(),
List::Big(c) => c.collect(a, b),
}
}
fn get(&self, i: usize) -> Option<TreeId> {
match self {
List::Small(v) => v.get(i).copied(),
List::Big(c) => c.get(i),
}
}
fn to_vec(&self) -> Vec<TreeId> {
self.collect(0, self.len())
}
fn holds_at(&self, lo: usize, run: &[TreeId]) -> bool {
let hi = lo.saturating_add(run.len());
match self {
List::Small(v) => v.get(lo..hi) == Some(run),
List::Big(c) => hi <= c.len() && c.collect(lo, hi) == run,
}
}
fn insert_run(&mut self, at: usize, run: &[TreeId]) {
match self {
List::Small(v) => {
let at = at.min(v.len());
v.splice(at..at, run.iter().copied());
if v.len() > BIG {
*self = List::Big(Box::new(Chunks::build(v)));
}
}
List::Big(c) => c.insert_run(at, run),
}
}
fn remove_range(&mut self, a: usize, b: usize) {
match self {
List::Small(v) => {
let b = b.min(v.len());
v.drain(a.min(b)..b);
}
List::Big(c) => {
c.remove_range(a, b);
if c.len() <= SMALL {
*self = List::Small(c.collect(0, c.len()));
}
}
}
}
}
type Lists = BTreeMap<String, List>;
#[derive(Clone, Debug, Default)]
pub(super) struct Index {
anchors: Lists,
aliases: Lists,
poisoned: bool,
}
fn pos(tree: &SyntaxTree, id: TreeId) -> usize {
tree.get(id).map_or(usize::MAX, TreeNode::preorder_index)
}
fn by_name<'n>(list: &[(&'n str, TreeId)]) -> BTreeMap<&'n str, Vec<TreeId>> {
let mut out: BTreeMap<&str, Vec<TreeId>> = BTreeMap::new();
for (name, id) in list {
out.entry(name).or_default().push(*id);
}
out
}
fn slice_of(tree: &SyntaxTree, list: &List, run: &[TreeId]) -> Option<usize> {
let first = *run.first()?;
let lo = list.lower_bound(tree, pos(tree, first));
list.holds_at(lo, run).then_some(lo)
}
fn merge_run(tree: &SyntaxTree, list: &mut List, run: &[TreeId]) {
let (Some(first), Some(last)) = (run.first(), run.last()) else {
return;
};
let (p0, p1) = (pos(tree, *first), pos(tree, *last));
let a = list.lower_bound(tree, p0);
let b = list.lower_bound(tree, p1.saturating_add(1));
if a == b {
list.insert_run(a, run);
return;
}
let mut old = list.to_vec().into_iter().peekable();
let mut new = run.iter().copied().peekable();
let mut out: Vec<TreeId> = Vec::with_capacity(old.len().saturating_add(run.len()));
loop {
let next = match (old.peek(), new.peek()) {
(Some(o), Some(n)) if pos(tree, *n) < pos(tree, *o) => new.next(),
(Some(_), _) => old.next(),
(None, Some(_)) => new.next(),
(None, None) => break,
};
if let Some(id) = next {
if out.last() != Some(&id) {
out.push(id);
}
}
}
*list = List::from_vec(out);
}
fn cut(list: &mut List, at: Option<usize>, gone: &[TreeId]) {
if let Some(lo) = at {
if list.holds_at(lo, gone) {
list.remove_range(lo, lo.saturating_add(gone.len()));
return;
}
}
let set: HashSet<TreeId> = gone.iter().copied().collect();
let mut ids = list.to_vec();
ids.retain(|x| !set.contains(x));
*list = List::from_vec(ids);
}
#[derive(Debug, Default)]
pub(super) struct Removal {
cuts: Vec<(bool, String, Vec<TreeId>, Option<usize>)>,
}
impl Index {
pub(super) fn build(tree: &SyntaxTree) -> Self {
let mut anchors = Lists::new();
let mut aliases = Lists::new();
Names::visit(tree.root(), tree.root(), |alias, name, id| {
let m = if alias { &mut aliases } else { &mut anchors };
match m.get_mut(name) {
Some(l) => l.push_built(id),
None => {
let mut l = List::default();
l.push_built(id);
m.insert(name.to_owned(), l);
}
}
});
for l in anchors.values_mut().chain(aliases.values_mut()) {
l.seal();
}
let poisoned = anchors.values().chain(aliases.values()).any(List::broken);
Self {
anchors,
aliases,
poisoned,
}
}
pub(super) fn poisoned(&self) -> bool {
self.poisoned
}
pub(super) fn plan_removal(&self, tree: &SyntaxTree, names: &Names<'_>) -> Removal {
let mut cuts = Vec::new();
for (alias, lists, of) in [
(false, &self.anchors, &names.anchors),
(true, &self.aliases, &names.aliases),
] {
for (name, ids) in by_name(of) {
let at = lists.get(name).and_then(|l| slice_of(tree, l, &ids));
cuts.push((alias, name.to_owned(), ids, at));
}
}
Removal { cuts }
}
pub(super) fn apply_removal(&mut self, removal: Removal) {
for (alias, name, ids, at) in removal.cuts {
let lists = if alias {
&mut self.aliases
} else {
&mut self.anchors
};
if let Some(list) = lists.get_mut(&name) {
cut(list, at, &ids);
self.poisoned |= list.broken();
if list.is_empty() {
lists.remove(&name);
}
}
}
}
pub(super) fn inserted(&mut self, tree: &SyntaxTree, names: &Names<'_>) {
for (name, ids) in by_name(&names.anchors) {
let list = self.anchors.entry(name.to_owned()).or_default();
merge_run(tree, list, &ids);
self.poisoned |= list.broken();
}
for (name, ids) in by_name(&names.aliases) {
let list = self.aliases.entry(name.to_owned()).or_default();
merge_run(tree, list, &ids);
self.poisoned |= list.broken();
}
}
pub(super) fn renamed(
&mut self,
tree: &SyntaxTree,
old: &str,
new: &str,
id: TreeId,
aliases: &[TreeId],
) {
self.forget(tree, old, id);
let list = self.anchors.entry(new.to_owned()).or_default();
merge_run(tree, list, &[id]);
self.poisoned |= list.broken();
if aliases.is_empty() {
return;
}
if let Some(list) = self.aliases.get_mut(old) {
let at = slice_of(tree, list, aliases);
cut(list, at, aliases);
self.poisoned |= list.broken();
if list.is_empty() {
self.aliases.remove(old);
}
}
let list = self.aliases.entry(new.to_owned()).or_default();
merge_run(tree, list, aliases);
self.poisoned |= list.broken();
}
fn range(tree: &SyntaxTree, list: &List, lo: usize, hi: usize) -> (usize, usize) {
let a = list.lower_bound(tree, lo);
let b = list.lower_bound(tree, hi).max(a);
(a, b)
}
pub(super) fn first_anchor_in(
&self,
tree: &SyntaxTree,
name: &str,
lo: usize,
hi: usize,
) -> Option<TreeId> {
let list = self.anchors.get(name)?;
let (a, b) = Self::range(tree, list, lo, hi);
(a < b).then(|| list.get(a)).flatten()
}
pub(super) fn aliases_in(
&self,
tree: &SyntaxTree,
name: &str,
lo: usize,
hi: usize,
) -> Vec<TreeId> {
self.aliases.get(name).map_or_else(Vec::new, |l| {
let (a, b) = Self::range(tree, l, lo, hi);
l.collect(a, b)
})
}
pub(super) fn aliases_exactly(
&self,
tree: &SyntaxTree,
name: &str,
point: usize,
end: usize,
run: Option<(TreeId, TreeId)>,
count: usize,
) -> bool {
let next = self
.first_anchor_in(tree, name, point, end)
.map_or(end, |id| pos(tree, id));
let Some(list) = self.aliases.get(name) else {
return count == 0 && run.is_none();
};
let (a, e) = Self::range(tree, list, point, end);
let n = list.lower_bound(tree, next).clamp(a, e);
if n != e || e.saturating_sub(a) != count {
return false;
}
match run {
None => count == 0,
Some((f, l)) => {
list.get(a) == Some(f) && e.checked_sub(1).and_then(|i| list.get(i)) == Some(l)
}
}
}
pub(super) fn nearest_before(
&self,
tree: &SyntaxTree,
name: &str,
from: usize,
at: usize,
) -> Option<TreeId> {
let list = self.anchors.get(name)?;
let (a, b) = Self::range(tree, list, from, at);
(a < b).then(|| list.get(b.saturating_sub(1))).flatten()
}
pub(super) fn next_after(&self, tree: &SyntaxTree, name: &str, at: usize, end: usize) -> usize {
self.first_anchor_in(tree, name, at.saturating_add(1), end)
.map_or(end, |id| pos(tree, id))
}
pub(super) fn aliases_of(
&self,
tree: &SyntaxTree,
name: &str,
at: usize,
end: usize,
) -> Vec<TreeId> {
let next = self.next_after(tree, name, at, end);
self.aliases_in(tree, name, at.saturating_add(1), next)
}
pub(super) fn forget(&mut self, tree: &SyntaxTree, name: &str, id: TreeId) {
if let Some(list) = self.anchors.get_mut(name) {
let at = slice_of(tree, list, &[id]);
cut(list, at, &[id]);
self.poisoned |= list.broken();
if list.is_empty() {
self.anchors.remove(name);
}
}
}
pub(super) fn has_anchor(&self, tree: &SyntaxTree, name: &str, lo: usize, hi: usize) -> bool {
self.first_anchor_in(tree, name, lo, hi).is_some()
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
use std::time::{Duration, Instant};
use super::*;
fn chunks(c: &Chunks) -> usize {
c.nodes.len() - c.free.len()
}
fn check(this: &Chunks) {
let mut todo = vec![this.root];
let mut seen = 0usize;
while let Some(n) = todo.pop() {
let Some(c) = this.at(n) else { continue };
seen += 1;
assert!(!c.ids.is_empty(), "empty chunk");
assert!(c.ids.len() <= CAP, "chunk over CAP");
assert_eq!(
c.size,
this.size(c.left) + this.size(c.right) + c.ids.len(),
"size"
);
for ch in [c.left, c.right] {
if ch != NIL {
assert!(this.prio(ch) <= c.prio, "heap order");
}
}
todo.push(c.left);
todo.push(c.right);
}
assert_eq!(seen, chunks(this), "arena leak");
}
fn tree(k: usize) -> SyntaxTree {
let mut s = String::from("anchor: &x 0\nsub:\n");
for i in 0..k {
s.push_str(&format!(" - &x {i}\n - *x\n"));
}
s.push_str("list:\n");
for _ in 0..k {
s.push_str(" - *x\n");
}
SyntaxTree::parse(s)
}
type Flat = Vec<(String, Vec<TreeId>)>;
fn lists(ix: &Index) -> (Flat, Flat) {
let flat = |l: &Lists| {
l.iter()
.map(|(k, v)| (k.clone(), v.to_vec()))
.collect::<Vec<_>>()
};
(flat(&ix.anchors), flat(&ix.aliases))
}
fn round_trip(t: &SyntaxTree) -> Duration {
let mut ix = Index::build(t);
let anchor = ix.anchors.get("x").unwrap().get(0).unwrap();
let sub = t
.root()
.descendants()
.find(|n| n.kind() == SyntaxKind::BlockSequence)
.unwrap();
let names = Names::of(sub, sub);
let list = ix.aliases.get("x").unwrap();
let aliases: Vec<TreeId> = list.collect(list.len() / 2, list.len());
let before = lists(&ix);
let start = Instant::now();
ix.renamed(t, "x", "y", anchor, &aliases);
ix.renamed(t, "y", "x", anchor, &aliases);
let plan = ix.plan_removal(t, &names);
ix.apply_removal(plan);
ix.inserted(t, &names);
let took = start.elapsed();
assert_eq!(lists(&ix), before);
took
}
#[test]
fn repeated_names_cost_linear_time() {
let (small, large) = (tree(2_000), tree(20_000));
let best = |t: &SyntaxTree| (0..3).map(|_| round_trip(t)).min().unwrap();
let (a, b) = (best(&small), best(&large));
let r = b.as_secs_f64() / a.as_secs_f64().max(1e-9);
assert!(
r < 30.0,
"index work is not linear: {a:?} vs {b:?} ({r:.1})"
);
}
#[test]
fn one_alias_in_a_long_list_costs_log_time() {
let alias_tree = |k: usize| {
let mut s = String::from("anchor: &x 0\nlist:\n");
for _ in 0..k {
s.push_str(" - *x\n");
}
SyntaxTree::parse(s)
};
let timed = |t: &SyntaxTree| {
let mut ix = Index::build(t);
let list = ix.aliases.get("x").unwrap();
let mid = list.get(list.len() / 2).unwrap();
let node = t.get(mid).unwrap();
let names = Names::of(node, node);
let before = lists(&ix);
let mut best = Duration::MAX;
for _ in 0..3 {
let start = Instant::now();
for _ in 0..200 {
let plan = ix.plan_removal(t, &names);
ix.apply_removal(plan);
ix.inserted(t, &names);
}
best = best.min(start.elapsed());
}
assert_eq!(lists(&ix), before);
best
};
let (small, large) = (alias_tree(1_000), alias_tree(500_000));
let (a, b) = (timed(&small), timed(&large));
let r = b.as_secs_f64() / a.as_secs_f64().max(1e-9);
eprintln!("one alias, 1k vs 500k: {a:?} vs {b:?} (ratio {r:.2})");
assert!(r < 8.0, "an edit costs more than log k: {r:.2}");
}
#[test]
fn merge_run_interleaved_and_repeated() {
let t = tree(3);
let ix = Index::build(&t);
let all = ix.aliases.get("x").unwrap().to_vec();
assert_eq!(all.len(), 6);
let mut list = List::from_vec(vec![all[0], all[2], all[4]]);
merge_run(&t, &mut list, &[all[1], all[2], all[3], all[5]]);
assert_eq!(list.to_vec(), all);
let mut list = List::from_vec(all.clone());
cut(&mut list, None, &[all[1], all[4]]);
assert_eq!(list.to_vec(), vec![all[0], all[2], all[3], all[5]]);
cut(&mut list, Some(0), &[all[3]]);
assert_eq!(list.to_vec(), vec![all[0], all[2], all[5]]);
let all_list = List::from_vec(all.clone());
assert_eq!(slice_of(&t, &all_list, &[all[2], all[3]]), Some(2));
assert_eq!(slice_of(&t, &all_list, &[all[2], all[4]]), None);
}
#[test]
fn chunks_match_a_vec_model() {
let t = tree(1_500);
let pool: Vec<TreeId> = t.root().descendants().map(|n| n.id()).collect();
let mut rng = 0x9e37_79b9_7f4a_7c15u64;
let mut next = |m: usize| {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
(rng % (m as u64).max(1)) as usize
};
let mut model: Vec<TreeId> = pool[..600].to_vec();
let mut c = Chunks::build(&model);
check(&c);
let mut fresh = 600usize;
for step in 0..4_000 {
let len = model.len();
if next(3) != 0 || len < 10 {
let n = 1 + next(if step % 50 == 0 { 300 } else { 6 });
let at = next(len + 1);
let run: Vec<TreeId> = (0..n).map(|i| pool[(fresh + i) % pool.len()]).collect();
fresh += n;
model.splice(at..at, run.iter().copied());
c.insert_run(at, &run);
} else {
let a = next(len);
let n = 1 + next(if step % 40 == 0 { len } else { 8 });
let b = (a + n).min(len);
model.drain(a..b);
c.remove_range(a, b);
}
check(&c);
assert_eq!(c.len(), model.len());
assert!(chunks(&c) <= model.len() / 16 + 1, "{} chunks", chunks(&c));
if step % 97 == 0 {
assert_eq!(c.collect(0, c.len()), model);
let a = next(model.len() + 1);
let b = a + next(model.len() + 1 - a);
assert_eq!(c.collect(a, b), model[a..b]);
}
}
assert_eq!(c.collect(0, c.len()), model);
}
#[test]
fn chunked_search_and_representation() {
let t = tree(800);
let ix = Index::build(&t);
let list = ix.aliases.get("x").unwrap();
assert!(matches!(list, List::Big(_)), "1,600 aliases: chunked");
let ids = list.to_vec();
for (i, id) in ids.iter().enumerate().step_by(37) {
let p = pos(&t, *id);
assert_eq!(list.lower_bound(&t, p), i);
assert_eq!(list.lower_bound(&t, p + 1), i + 1);
assert_eq!(list.get(i), Some(*id));
}
assert_eq!(list.lower_bound(&t, 0), 0);
assert_eq!(list.lower_bound(&t, usize::MAX), ids.len());
let mut l = List::from_vec(ids[..300].to_vec());
assert!(matches!(l, List::Big(_)));
l.remove_range(0, 200);
assert!(matches!(l, List::Big(_)), "100 ids: still chunked");
l.remove_range(0, 40);
assert!(matches!(l, List::Small(_)), "60 ids: a Vec again");
assert_eq!(l.to_vec(), ids[240..300]);
l.insert_run(0, &ids[..240]);
assert_eq!(l.to_vec(), ids[..300]);
assert!(matches!(l, List::Big(_)));
}
#[test]
fn names_of_a_range() {
let t = SyntaxTree::parse("a: &x [&y 1, *x, *y]\nb: &z {c: *z}\n");
let n = Names::of(t.root(), t.root());
let a: Vec<&str> = n.anchors.iter().map(|x| x.0).collect();
let b: Vec<&str> = n.aliases.iter().map(|x| x.0).collect();
assert_eq!((a, b), (vec!["x", "y", "z"], vec!["x", "y", "z"]));
for (_, id) in &n.anchors {
assert!(t.get(*id).unwrap().anchor().is_some());
}
let anchor = t
.root()
.descendants()
.find(|x| x.kind() == SyntaxKind::Anchor)
.unwrap();
assert!(Names::of(anchor, anchor).is_empty());
}
}