ebox/native/src/line_metrics.rs

296 lines
8.9 KiB
Rust

//! Persistent numeric summaries. No node retains a line, document, or old projection.
use std::ops::Range;
use std::sync::Arc;
use super::{record, Line, LinePlanWork};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct LineMetric {
pub width: i64,
pub chars: u64,
pub min_content: i64,
pub nonempty: bool,
pub whitespace: bool,
pub noncontent: bool,
}
impl LineMetric {
pub fn line(line: &Line) -> Self {
Self {
width: line.width,
chars: line.atoms.char_count(),
min_content: line.atoms.min_content_width(),
nonempty: !line.atoms.is_empty(),
whitespace: line.whitespace_only(),
noncontent: line.has_noncontent_properties(),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(super) struct Summary {
pub count: usize,
pub chars: u64,
pub width_sum: i64,
pub first_width: i64,
pub min_width: i64,
pub max_width: i64,
pub min_content: i64,
pub all_nonempty: bool,
}
impl Summary {
fn line(line: LineMetric) -> Self {
Self {
count: 1,
chars: line.chars,
width_sum: line.width,
first_width: line.width,
min_width: line.width,
max_width: line.width,
min_content: line.min_content,
all_nonempty: line.nonempty,
}
}
fn combine(self, other: Self) -> Self {
if self.count == 0 {
return other;
}
if other.count == 0 {
return self;
}
Self {
count: self
.count
.checked_add(other.count)
.expect("validated line count"),
chars: self
.chars
.checked_add(other.chars)
.expect("validated character count"),
width_sum: self
.width_sum
.checked_add(other.width_sum)
.expect("validated line widths"),
first_width: self.first_width,
min_width: self.min_width.min(other.min_width),
max_width: self.max_width.max(other.max_width),
min_content: self.min_content.max(other.min_content),
all_nonempty: self.all_nonempty && other.all_nonempty,
}
}
}
#[derive(Clone, Debug, Default)]
pub(super) struct Metrics(Option<Arc<Node>>);
#[derive(Debug)]
struct Node {
summary: Summary,
height: u32,
kind: Kind,
}
#[derive(Debug)]
enum Kind {
Leaf(Box<[LineMetric]>),
Branch(Metrics, Metrics),
}
impl Metrics {
pub fn from_iter(lines: impl IntoIterator<Item = LineMetric>) -> Self {
let mut lines = lines.into_iter();
let mut blocks = Vec::new();
loop {
let block = lines.by_ref().take(16).collect::<Vec<_>>();
if block.is_empty() {
break;
}
blocks.push(Self::leaf(block.into_boxed_slice()));
}
fn build(lines: &[Metrics]) -> Metrics {
match lines {
[] => Metrics::default(),
[line] => line.clone(),
_ => Metrics::branch(
build(&lines[..lines.len() / 2]),
build(&lines[lines.len() / 2..]),
),
}
}
build(&blocks)
}
fn line(line: LineMetric) -> Self {
Self::leaf(Box::new([line]))
}
fn leaf(lines: Box<[LineMetric]>) -> Self {
record(LinePlanWork {
metric_nodes_created: 1,
metric_entries_written: lines.len() as u64,
..LinePlanWork::default()
});
let summary = lines
.iter()
.copied()
.fold(Summary::default(), |summary, line| {
summary.combine(Summary::line(line))
});
Self(Some(Arc::new(Node {
summary,
height: 1,
kind: Kind::Leaf(lines),
})))
}
fn height(&self) -> u32 {
self.0.as_ref().map_or(0, |node| node.height)
}
pub fn summary(&self) -> Summary {
self.0
.as_ref()
.map_or(Summary::default(), |node| node.summary)
}
fn branch(left: Self, right: Self) -> Self {
if left.0.is_none() {
return right;
}
if right.0.is_none() {
return left;
}
record(LinePlanWork {
metric_nodes_created: 1,
..LinePlanWork::default()
});
Self(Some(Arc::new(Node {
summary: left.summary().combine(right.summary()),
height: left.height().max(right.height()) + 1,
kind: Kind::Branch(left, right),
})))
}
fn children(&self) -> (&Self, &Self) {
let Kind::Branch(left, right) = &self.0.as_ref().expect("nonempty metrics").kind else {
unreachable!("metric balance branch")
};
(left, right)
}
fn balanced(left: Self, right: Self) -> Self {
if left.height() > right.height() + 1 {
let (ll, lr) = left.children();
if ll.height() >= lr.height() {
Self::branch(ll.clone(), Self::branch(lr.clone(), right))
} else {
let (lrl, lrr) = lr.children();
Self::branch(
Self::branch(ll.clone(), lrl.clone()),
Self::branch(lrr.clone(), right),
)
}
} else if right.height() > left.height() + 1 {
let (rl, rr) = right.children();
if rr.height() >= rl.height() {
Self::branch(Self::branch(left, rl.clone()), rr.clone())
} else {
let (rll, rlr) = rl.children();
Self::branch(
Self::branch(left, rll.clone()),
Self::branch(rlr.clone(), rr.clone()),
)
}
} else {
Self::branch(left, right)
}
}
pub fn concat(&self, other: &Self) -> Self {
if self.height() > other.height() + 1 {
let (left, right) = self.children();
Self::balanced(left.clone(), right.concat(other))
} else if other.height() > self.height() + 1 {
let (left, right) = other.children();
Self::balanced(self.concat(left), right.clone())
} else {
Self::branch(self.clone(), other.clone())
}
}
#[cfg(test)]
pub fn get(&self, index: usize) -> Option<LineMetric> {
let node = self.0.as_ref()?;
record(LinePlanWork {
metric_nodes_visited: 1,
..LinePlanWork::default()
});
match &node.kind {
Kind::Leaf(lines) => lines.get(index).copied(),
Kind::Branch(left, right) => {
if index < left.summary().count {
left.get(index)
} else {
right.get(index - left.summary().count)
}
}
}
}
pub fn slice(&self, range: Range<usize>) -> Self {
assert!(range.start <= range.end && range.end <= self.summary().count);
if range.is_empty() {
return Self::default();
}
if range.start == 0 && range.end == self.summary().count {
return self.clone();
}
record(LinePlanWork {
metric_nodes_visited: 1,
..LinePlanWork::default()
});
if let Kind::Leaf(lines) = &self.0.as_ref().expect("nonempty metrics").kind {
return Self::leaf(lines[range].into());
}
let (left, right) = self.children();
let middle = left.summary().count;
if range.end <= middle {
left.slice(range)
} else if range.start >= middle {
right.slice(range.start - middle..range.end - middle)
} else {
left.slice(range.start..middle)
.concat(&right.slice(0..range.end - middle))
}
}
pub fn replace(&self, index: usize, line: LineMetric) -> Self {
self.slice(0..index)
.concat(&Self::line(line))
.concat(&self.slice(index + 1..self.summary().count))
}
pub fn iter(&self) -> impl Iterator<Item = LineMetric> + '_ {
let mut stack = self.0.as_deref().into_iter().collect::<Vec<_>>();
let mut leaf = [].iter();
std::iter::from_fn(move || loop {
if let Some(line) = leaf.next() {
return Some(*line);
}
let node = stack.pop()?;
record(LinePlanWork {
metric_nodes_visited: 1,
..LinePlanWork::default()
});
match &node.kind {
Kind::Leaf(lines) => leaf = lines.iter(),
Kind::Branch(left, right) => {
stack.push(right.0.as_deref().expect("nonempty metric branch"));
stack.push(left.0.as_deref().expect("nonempty metric branch"));
}
}
})
}
}