use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use oneyaml_core::Diagnostic;
use crate::diag_cap::Dropped;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct Agg {
pub(super) n: usize,
pub(super) errors: usize,
pub(super) dropped: Dropped,
}
#[derive(Clone, Debug)]
struct Fenwick(Vec<u32>);
impl Fenwick {
fn from_leaves(mut t: Vec<u32>) -> Self {
let len = t.len();
for k in 1..=len {
let parent = k.wrapping_add(k & k.wrapping_neg());
if parent <= len {
let v = t.get(k.wrapping_sub(1)).copied().unwrap_or(0);
if let Some(p) = t.get_mut(parent.wrapping_sub(1)) {
*p = p.wrapping_add(v);
}
}
}
Self(t)
}
fn add(&mut self, i: usize, delta: u32) {
let len = self.0.len();
let mut k = i.wrapping_add(1);
while k <= len {
if let Some(v) = self.0.get_mut(k.wrapping_sub(1)) {
*v = v.wrapping_add(delta);
}
k = k.wrapping_add(k & k.wrapping_neg());
}
}
fn prefix(&self, j: usize) -> u32 {
let mut k = j.min(self.0.len());
let mut sum = 0u32;
while k > 0 {
sum = sum.wrapping_add(self.0.get(k.wrapping_sub(1)).copied().unwrap_or(0));
k &= k.wrapping_sub(1);
}
sum
}
fn leaf(&self, i: usize) -> u32 {
self.prefix(i.saturating_add(1))
.wrapping_sub(self.prefix(i))
}
fn find(&self, t: u32, total: u32) -> Option<(usize, u32)> {
if total <= t {
return None;
}
let len = self.0.len();
let mut step = usize::from(len > 0);
while step.checked_mul(2).is_some_and(|s| s <= len) {
step = step.saturating_mul(2);
}
let mut at = 0usize;
let mut rest = t;
while step > 0 {
let next = at.wrapping_add(step);
if next <= len {
let v = self.0.get(next.wrapping_sub(1)).copied().unwrap_or(0);
if v <= rest {
at = next;
rest = rest.wrapping_sub(v);
}
}
step /= 2;
}
(at < len).then_some((at, rest))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum ErrorAt {
At(usize),
Unknown,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct Seen {
pub(super) start: usize,
pub(super) at: usize,
pub(super) len: usize,
pub(super) spare: bool,
pub(super) error: ErrorAt,
}
#[derive(Clone, Debug, Default)]
pub(super) struct Shown {
pub(super) list: Option<Arc<[Diagnostic]>>,
pub(super) spare: Option<Diagnostic>,
pub(super) docs: BTreeMap<usize, Seen>,
}
#[derive(Clone, Debug)]
pub(super) struct CapIndex {
n: Fenwick,
errors: Fenwick,
total_n: u32,
total_errors: u32,
meaning: Vec<[u32; 2]>,
dropped: BTreeMap<usize, Dropped>,
firsts: BTreeSet<(usize, usize)>,
dropped_count: usize,
stale: BTreeSet<usize>,
pub(super) shown: Shown,
}
pub(super) trait Parts {
fn syntax_part(&self, i: usize) -> Agg;
fn meaning_part(&self, i: usize) -> Agg;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct Total {
pub(super) n: usize,
pub(super) errors: usize,
pub(super) dropped: Dropped,
}
fn small(v: usize) -> Option<u32> {
u32::try_from(v).ok()
}
fn wide(v: u32) -> usize {
usize::try_from(v).unwrap_or(usize::MAX)
}
impl CapIndex {
pub(super) fn build(docs: usize, parts: &impl Parts) -> Option<Self> {
let mut n = Vec::with_capacity(docs);
let mut errors = Vec::with_capacity(docs);
let mut meaning = Vec::with_capacity(docs);
let mut ix = Self {
n: Fenwick(Vec::new()),
errors: Fenwick(Vec::new()),
total_n: 0,
total_errors: 0,
meaning: Vec::new(),
dropped: BTreeMap::new(),
firsts: BTreeSet::new(),
dropped_count: 0,
stale: BTreeSet::new(),
shown: Shown::default(),
};
let (mut total_n, mut total_errors) = (0u32, 0u32);
for i in 0..docs {
let s = parts.syntax_part(i);
let m = parts.meaning_part(i);
let mn = [small(m.n)?, small(m.errors)?];
let dn = small(s.n)?.checked_add(mn[0])?;
let de = small(s.errors)?.checked_add(mn[1])?;
total_n = total_n.checked_add(dn)?;
total_errors = total_errors.checked_add(de)?;
n.push(dn);
errors.push(de);
meaning.push(mn);
ix.set_dropped(i, m.dropped);
}
ix.n = Fenwick::from_leaves(n);
ix.errors = Fenwick::from_leaves(errors);
ix.total_n = total_n;
ix.total_errors = total_errors;
ix.meaning = meaning;
Some(ix)
}
fn set_dropped(&mut self, i: usize, d: Dropped) {
if let Some(old) = self.dropped.remove(&i) {
self.dropped_count = self.dropped_count.saturating_sub(old.count);
if let Some(f) = old.first {
self.firsts.remove(&(f, i));
}
}
if d != Dropped::default() {
self.dropped_count = self.dropped_count.saturating_add(d.count);
if let Some(f) = d.first {
self.firsts.insert((f, i));
}
self.dropped.insert(i, d);
}
}
pub(super) fn mark(&mut self, i: usize) {
self.stale.insert(i);
}
pub(super) fn refresh(&mut self, parts: &impl Parts) -> bool {
let stale = std::mem::take(&mut self.stale);
let len = self.meaning.len();
let mut syntax = BTreeSet::new();
for &i in stale.iter().filter(|i| **i < len) {
syntax.extend(i.checked_sub(1));
syntax.insert(i);
syntax.extend(i.checked_add(1).filter(|j| *j < len));
}
for &i in &syntax {
let old_m = self.meaning.get(i).copied().unwrap_or([0, 0]);
let new_m = if stale.contains(&i) {
let m = parts.meaning_part(i);
self.set_dropped(i, m.dropped);
match (small(m.n), small(m.errors)) {
(Some(a), Some(b)) => [a, b],
_ => return false,
}
} else {
old_m
};
let s = parts.syntax_part(i);
let (Some(sn), Some(se)) = (small(s.n), small(s.errors)) else {
return false;
};
let (Some(dn), Some(de)) = (sn.checked_add(new_m[0]), se.checked_add(new_m[1])) else {
return false;
};
let (was_n, was_e) = (self.n.leaf(i), self.errors.leaf(i));
let both = i.checked_sub(1).is_some_and(|p| stale.contains(&p))
&& stale.contains(&i.saturating_add(1));
if stale.contains(&i) || both || (dn, de) != (was_n, was_e) {
self.shown.docs.remove(&i);
}
let moved = |total: u32, now: u32, was: u32| {
u64::from(total)
.checked_add(u64::from(now))
.and_then(|v| v.checked_sub(u64::from(was)))
.and_then(|v| u32::try_from(v).ok())
};
let (Some(total_n), Some(total_e)) = (
moved(self.total_n, dn, was_n),
moved(self.total_errors, de, was_e),
) else {
return false;
};
self.n.add(i, dn.wrapping_sub(was_n));
self.errors.add(i, de.wrapping_sub(was_e));
self.total_n = total_n;
self.total_errors = total_e;
if let Some(m) = self.meaning.get_mut(i) {
*m = new_m;
}
}
true
}
pub(super) fn total(&self) -> Total {
Total {
n: wide(self.total_n),
errors: wide(self.total_errors),
dropped: Dropped {
count: self.dropped_count,
first: self.firsts.first().map(|(f, _)| *f),
},
}
}
pub(super) fn leaf_n(&self, i: usize) -> usize {
wide(self.n.leaf(i))
}
pub(super) fn leaf_errors(&self, i: usize) -> usize {
wide(self.errors.leaf(i))
}
pub(super) fn meaning_empty(&self, i: usize) -> bool {
self.meaning.get(i).is_some_and(|m| m[0] == 0) && !self.dropped.contains_key(&i)
}
pub(super) fn nth_diagnostic(&self, t: usize) -> Option<(usize, usize)> {
let t = small(t)?;
self.n
.find(t, self.total_n)
.map(|(d, before)| (d, wide(before)))
}
pub(super) fn first_error_from(&self, j: usize) -> Option<usize> {
let before = self.errors.prefix(j);
self.errors.find(before, self.total_errors).map(|(d, _)| d)
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
use super::*;
struct Fixed(Vec<(usize, usize)>);
impl Parts for Fixed {
fn syntax_part(&self, i: usize) -> Agg {
Agg {
n: i % 2,
errors: 0,
dropped: Dropped::default(),
}
}
fn meaning_part(&self, i: usize) -> Agg {
let (n, errors) = self.0[i];
Agg {
n,
errors,
dropped: if n > 3 {
Dropped::one(i * 10)
} else {
Dropped::default()
},
}
}
}
#[test]
fn answers_equal_a_scan() {
for d in 0..40usize {
let mut f = Fixed((0..d).map(|i| ((i * 7) % 5, (i * 3) % 2)).collect());
let mut ix = CapIndex::build(d, &f).unwrap();
for round in 0..3 {
let each = |i: usize| f.0[i].0 + i % 2;
let total: usize = (0..d).map(each).sum();
assert_eq!(ix.total().n, total);
assert_eq!(ix.total().errors, f.0.iter().map(|x| x.1).sum::<usize>());
let first = f.0.iter().position(|x| x.0 > 3).map(|i| i * 10);
assert_eq!(ix.total().dropped.first, first);
let count = f.0.iter().filter(|x| x.0 > 3).count();
assert_eq!(ix.total().dropped.count, count);
let mut t = 0;
for i in 0..d {
assert_eq!(ix.leaf_n(i), each(i));
assert_eq!(ix.leaf_errors(i), f.0[i].1);
assert_eq!(ix.meaning_empty(i), f.0[i].0 == 0);
for k in 0..each(i) {
assert_eq!(ix.nth_diagnostic(t), Some((i, k)));
t += 1;
}
}
assert_eq!(ix.nth_diagnostic(t), None);
for j in 0..=d {
let want = (j..d).find(|i| f.0[*i].1 > 0);
assert_eq!(ix.first_error_from(j), want, "d {d} j {j}");
}
for i in (round..d).step_by(3) {
f.0[i] = ((f.0[i].0 + round + 2) % 6, (f.0[i].1 + 1) % 2);
ix.mark(i);
}
assert!(ix.refresh(&f));
}
}
}
#[test]
fn counts_past_u32_give_no_index() {
struct Big;
impl Parts for Big {
fn syntax_part(&self, _: usize) -> Agg {
Agg {
n: u32::MAX as usize,
errors: 0,
dropped: Dropped::default(),
}
}
fn meaning_part(&self, _: usize) -> Agg {
Agg::default()
}
}
assert!(CapIndex::build(1, &Big).is_some());
assert!(CapIndex::build(2, &Big).is_none());
}
}