use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use super::mutate::Arena;
use super::{Document, NodeData, NodeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BlockEdit {
Append(NodeId),
Without(NodeId),
InsertAt(usize, NodeId),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Caps(BTreeMap<u32, u32>);
impl Caps {
pub(crate) const fn new() -> Self {
Self(BTreeMap::new())
}
fn of(&self, start: usize, len: usize) -> usize {
if len == 0 {
return 0;
}
let start = u32::try_from(start).unwrap_or(u32::MAX);
self.0.get(&start).map_or(len, |c| (*c as usize).max(len))
}
fn moved(&mut self, from: usize, to: usize, cap: usize) {
let _ = self.0.remove(&u32::try_from(from).unwrap_or(u32::MAX));
let _ = self.0.insert(
u32::try_from(to).unwrap_or(u32::MAX),
u32::try_from(cap).unwrap_or(u32::MAX),
);
}
fn remember(&mut self, start: usize, len: usize) {
let start = u32::try_from(start).unwrap_or(u32::MAX);
let len = u32::try_from(len).unwrap_or(u32::MAX);
let entry = self.0.entry(start).or_insert(len);
*entry = (*entry).max(len);
}
fn shrunk(&mut self, start: usize, len: usize) {
if len == 1 {
let _ = self.0.remove(&u32::try_from(start).unwrap_or(u32::MAX));
} else {
self.remember(start, len);
}
}
pub(crate) fn clear(&mut self) {
self.0.clear();
}
}
pub(crate) fn pack(start: usize, len: usize) -> (u32, u32) {
(
u32::try_from(start).unwrap_or(u32::MAX),
u32::try_from(len).unwrap_or(u32::MAX),
)
}
pub(crate) fn relocate_block(
arena: &mut Vec<NodeId>,
caps: &mut Caps,
range: (u32, u32),
edit: BlockEdit,
) -> (u32, u32) {
let (start, len) = (range.0 as usize, range.1 as usize);
let (start, len) = if start.saturating_add(len) > arena.len() {
(0, 0)
} else {
(start, len)
};
match edit {
BlockEdit::Append(id) => insert_into(arena, caps, start, len, len, id),
BlockEdit::InsertAt(at, id) => {
insert_into(arena, caps, start, len, at.min(len), id)
}
BlockEdit::Without(id) => drop_from(arena, caps, start, len, id),
}
}
fn insert_into(
arena: &mut Vec<NodeId>,
caps: &mut Caps,
start: usize,
len: usize,
at: usize,
id: NodeId,
) -> (u32, u32) {
let split = start + at;
if len < caps.of(start, len) {
arena.copy_within(split..start + len, split + 1);
arena[split] = id;
return pack(start, len + 1);
}
move_block_with(arena, caps, start..start + len, split, id)
}
fn move_block_with(
arena: &mut Vec<NodeId>,
caps: &mut Caps,
block: core::ops::Range<usize>,
split: usize,
id: NodeId,
) -> (u32, u32) {
let len = block.len();
let new_start = arena.len();
let cap = (2 * len).max(4);
arena.extend_from_within(block.start..split);
arena.push(id);
arena.extend_from_within(split..block.end);
arena.resize(new_start + cap, NodeId::UNUSED);
caps.moved(block.start, new_start, cap);
pack(new_start, len + 1)
}
fn drop_from(
arena: &mut [NodeId],
caps: &mut Caps,
start: usize,
len: usize,
id: NodeId,
) -> (u32, u32) {
let end = start + len;
let Some(at) = arena[start..end].iter().position(|c| *c == id) else {
return pack(start, len);
};
let hole = start + at;
arena.copy_within(hole + 1..end, hole);
arena[end - 1] = NodeId::UNUSED;
caps.shrunk(start, len);
pack(start, len - 1)
}
impl Document {
pub(super) fn block_range(
&self,
owner: NodeId,
arena: Arena,
) -> Option<(u32, u32)> {
let node = self.resolve(owner)?;
match (arena, &node.data) {
(Arena::Children, _) => Some(node.children),
(Arena::Attributes, NodeData::Element { attributes, .. }) => {
Some(*attributes)
}
(Arena::Namespaces, NodeData::Element { namespaces, .. }) => {
Some(*namespaces)
}
_ => None,
}
}
pub(crate) fn set_block_range(
&mut self,
owner: NodeId,
arena: Arena,
range: (u32, u32),
) {
let Some(node) = self.resolve_mut(owner) else {
return;
};
match arena {
Arena::Children => node.children = range,
Arena::Attributes => {
if let NodeData::Element { attributes, .. } = &mut node.data {
*attributes = range;
}
}
Arena::Namespaces => {
if let NodeData::Element { namespaces, .. } = &mut node.data {
*namespaces = range;
}
}
}
}
pub(super) fn edit_block(
&mut self,
owner: NodeId,
arena: Arena,
edit: BlockEdit,
) -> bool {
let Some(range) = self.block_range(owner, arena) else {
return false;
};
let moved = match arena {
Arena::Children => relocate_block(
&mut self.child_ids,
&mut self.child_caps,
range,
edit,
),
Arena::Attributes => relocate_block(
&mut self.attr_ids,
&mut self.attr_caps,
range,
edit,
),
Arena::Namespaces => {
relocate_block(&mut self.ns_ids, &mut self.ns_caps, range, edit)
}
};
self.set_block_range(owner, arena, moved);
self.order_dirty = true;
true
}
pub(super) fn unlink_from_parent(&mut self, id: NodeId) {
if let Some(parent) = self.parent(id) {
let _ = self.edit_block(
parent,
Arena::Children,
BlockEdit::Without(id),
);
}
}
pub(super) fn append_node(
&mut self,
parent: NodeId,
data: NodeData,
) -> NodeId {
let id = self.alloc_node(data, Some(parent));
let _ = self.edit_block(parent, Arena::Children, BlockEdit::Append(id));
id
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(n: u32) -> NodeId {
NodeId {
index: n,
generation: 0,
}
}
fn block(arena: &[NodeId], range: (u32, u32)) -> Vec<u32> {
let (s, l) = (range.0 as usize, range.1 as usize);
arena[s..s + l].iter().map(|n| n.index).collect()
}
#[test]
fn appends_after_a_move_are_in_place() {
let mut arena = Vec::new();
let mut caps = Caps::new();
let mut range = (0, 0);
let mut moves = 0;
for i in 0..1_000 {
let before = range.0;
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::Append(id(i)),
);
if range.0 != before {
moves += 1;
}
}
assert_eq!(block(&arena, range), (0..1_000).collect::<Vec<_>>());
assert!(moves <= 10, "{moves} moves for 1,000 appends");
assert!(arena.len() < 4_000, "arena is {} entries", arena.len());
}
#[test]
fn an_empty_block_never_writes_into_someone_elses() {
let mut arena = Vec::new();
let mut caps = Caps::new();
let first = relocate_block(
&mut arena,
&mut caps,
(0, 0),
BlockEdit::Append(id(1)),
);
let second = relocate_block(
&mut arena,
&mut caps,
(0, 0),
BlockEdit::Append(id(2)),
);
assert_eq!(block(&arena, first), [1]);
assert_eq!(block(&arena, second), [2]);
assert_ne!(first.0, second.0);
}
#[test]
fn removal_shifts_in_place_and_frees_capacity() {
let mut arena = Vec::new();
let mut caps = Caps::new();
let mut range = (0, 0);
for i in 0..4 {
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::Append(id(i)),
);
}
let len_before = arena.len();
let after = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::Without(id(1)),
);
assert_eq!(after.0, range.0, "no move");
assert_eq!(block(&arena, after), [0, 2, 3]);
assert_eq!(arena.len(), len_before, "no garbage");
let again = relocate_block(
&mut arena,
&mut caps,
after,
BlockEdit::Append(id(9)),
);
assert_eq!(again.0, after.0, "the freed entry is reused");
assert_eq!(block(&arena, again), [0, 2, 3, 9]);
let missing = relocate_block(
&mut arena,
&mut caps,
again,
BlockEdit::Without(id(42)),
);
assert_eq!(
missing, again,
"removing what is not there changes nothing"
);
}
#[test]
fn insertion_keeps_order_whether_or_not_the_block_moves() {
let mut arena = Vec::new();
let mut caps = Caps::new();
let mut range = (0, 0);
for i in [0, 2] {
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::Append(id(i)),
);
}
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::InsertAt(1, id(1)),
);
assert_eq!(block(&arena, range), [0, 1, 2]);
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::Append(id(3)),
);
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::InsertAt(0, id(7)),
);
assert_eq!(block(&arena, range), [7, 0, 1, 2, 3]);
range = relocate_block(
&mut arena,
&mut caps,
range,
BlockEdit::InsertAt(99, id(8)),
);
assert_eq!(
block(&arena, range),
[7, 0, 1, 2, 3, 8],
"past the end appends"
);
}
#[test]
fn a_range_outside_the_arena_is_treated_as_empty() {
let mut arena = Vec::new();
let mut caps = Caps::new();
let range = relocate_block(
&mut arena,
&mut caps,
(50, 3),
BlockEdit::Append(id(1)),
);
assert_eq!(block(&arena, range), [1]);
}
}