Skip to content

Commit

Permalink
feat(cordyceps): add Cursor::split_before/after (#233)
Browse files Browse the repository at this point in the history
This commit adds `split_before` and `split_after` methods to
`cordyceps::list::Cursor`, similar to the ones on
`std::collections::linked_list::CursorMut`.

Signed-off-by: Eliza Weisman <[email protected]>
  • Loading branch information
hawkw committed Jun 21, 2022
1 parent 48167ce commit 1093c36
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 6 deletions.
12 changes: 7 additions & 5 deletions cordyceps/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,16 +874,18 @@ impl<T: Linked<Links<T>> + ?Sized> List<T> {
// the head of the new list is the split node's `next` node (which is
// replaced with `None`)
let head = unsafe { T::links(split_node).as_mut().set_next(None) };
if let Some(head) = head {
let tail = if let Some(head) = head {
// since `head` is now the head of its own list, it has no `prev`
// link any more.
let _prev = unsafe { T::links(head).as_mut().set_prev(None) };
debug_assert_eq!(_prev, Some(split_node));
}

// the tail of the new list is this list's old tail, if the split list
// is not empty.
let tail = self.tail.replace(split_node);
// the tail of the new list is this list's old tail, if the split list
// is not empty.
self.tail.replace(split_node)
} else {
None
};

let split = Self {
head,
Expand Down
44 changes: 43 additions & 1 deletion cordyceps/src/list/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{Link, Links, List};
use crate::{util::FmtOption, Linked};
use core::{fmt, pin::Pin, ptr::NonNull};
use core::{fmt, mem, pin::Pin, ptr::NonNull};

/// A cursor over a [`List`].
///
Expand Down Expand Up @@ -267,6 +267,48 @@ impl<'a, T: Linked<Links<T>> + ?Sized> Cursor<'a, T> {
self.list.is_empty()
}

/// Splits the list into two after the current element. This will return a
/// new list consisting of everything after the cursor, with the original
/// list retaining everything before.
///
/// If the cursor is pointing at the null element, then the entire contents
/// of the `List` are moved.
pub fn split_after(&mut self) -> List<T> {
let split_at = if self.index == self.list.len {
self.index = 0;
0
} else {
self.index + 1
};
unsafe {
// safety: we know we are splitting at a node that belongs to our list.
self.list.split_after_node(self.curr, split_at)
}
}

/// Splits the list into two before the current element. This will return a
/// new list consisting of everything before the cursor, with the original
/// list retaining everything after the cursor.
///
/// If the cursor is pointing at the null element, then the entire contents
/// of the `List` are moved.
pub fn split_before(&mut self) -> List<T> {
let split_at = self.index;
self.index = 0;

let split = if split_at == self.list.len() {
// if we're at the end of the list, "before" is the whole thing.
List::new()
} else {
unsafe {
let node = self.curr.and_then(|curr| T::links(curr).as_mut().prev());
// safety: we know we are splitting at a node that belongs to our list.
self.list.split_after_node(node, split_at)
}
};
mem::replace(self.list, split)
}

#[inline(always)]
fn next_link(&self) -> Link<T> {
match self.curr {
Expand Down
82 changes: 82 additions & 0 deletions cordyceps/src/list/tests/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,85 @@ fn cursor_mut_insert() {
// &[200, 201, 202, 203, 1, 100, 101]
// );
}

#[test]
fn cursor_mut_split_after() {
let _trace = trace_init();
let entries = [entry(1), entry(2), entry(3), entry(4), entry(5)];
let vals = [1, 2, 3, 4, 5];

// test all splits
for i in 0..vals.len() + 1 {
let _span = tracing::info_span!("split_after", i).entered();

let mut list = list_from_iter(&entries);
let mut cursor = list.cursor_front_mut();

for _ in 0..i {
cursor.move_next();
}

tracing::info!(?cursor);

// split off at this index
let split = cursor.split_after();
tracing::info!(?split, ?list);

assert_valid!(list);
assert_valid!(split);

let split_entries = split.iter().map(|entry| entry.val).collect::<Vec<_>>();
let list_entries = list.iter().map(|entry| entry.val).collect::<Vec<_>>();
tracing::info!(?list_entries, ?split_entries);

if i < vals.len() {
assert_eq!(list_entries, vals[..i + 1], "list after split");
assert_eq!(split_entries, vals[i + 1..], "split");
} else {
// if we were at the end of the list, the split should contain
// everything.
assert_eq!(list_entries, &[], "list after split");
assert_eq!(split_entries, vals[..], "split");
}
}
}

#[test]
fn cursor_mut_split_before() {
let _trace = trace_init();
let entries = [entry(1), entry(2), entry(3), entry(4), entry(5)];
let vals = [1, 2, 3, 4, 5];

// test all splits
for i in 0..vals.len() + 1 {
let _span = tracing::info_span!("split_before", i).entered();
let mut list = list_from_iter(&entries);
let mut cursor = list.cursor_front_mut();
for _ in 0..i {
cursor.move_next();
}

tracing::info!(?cursor);

// split off at this index
let split = cursor.split_before();
tracing::info!(?split, ?list);

assert_valid!(list);
assert_valid!(split);

let split_entries = split.iter().map(|entry| entry.val).collect::<Vec<_>>();
let list_entries = list.iter().map(|entry| entry.val).collect::<Vec<_>>();
tracing::info!(?list_entries, ?split_entries);

if i > 0 {
assert_eq!(list_entries, vals[i..], "list after split");
assert_eq!(split_entries, vals[..i], "split");
} else {
// if we were at the beginning of the list, the split should contain
// everything.
assert_eq!(list_entries, vals[..], "list after split");
assert_eq!(split_entries, &[], "split");
}
}
}

0 comments on commit 1093c36

Please sign in to comment.