From cd26ddcf8f173c87c28417ced191d6400e3f3570 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 2 Aug 2026 06:22:23 +0300 Subject: [PATCH] Revert "Remove mutable api" --- Cargo.toml | 2 +- src/api.rs | 31 +++- src/ast.rs | 7 + src/cursor.rs | 361 +++++++++++++++++++++++++++++++++++++++---- src/green/element.rs | 9 ++ src/green/node.rs | 9 +- src/green/token.rs | 9 +- src/lib.rs | 2 + src/sll.rs | 129 ++++++++++++++++ src/utility_types.rs | 28 +++- 10 files changed, 553 insertions(+), 34 deletions(-) create mode 100644 src/sll.rs diff --git a/Cargo.toml b/Cargo.toml index 097c7432..8ecfcfcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rowan" -version = "0.17.0" +version = "0.15.19" authors = ["Aleksey Kladov "] repository = "https://github.com/rust-analyzer/rowan" license = "MIT OR Apache-2.0" diff --git a/src/api.rs b/src/api.rs index 9b7ca5a1..7aaac5b7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ -use std::{fmt, iter, marker::PhantomData}; +use std::{borrow::Cow, fmt, iter, marker::PhantomData, ops::Range}; use crate::{ cursor, green::GreenTokenData, Direction, GreenNode, GreenNodeData, GreenToken, NodeOrToken, @@ -121,7 +121,7 @@ impl SyntaxNode { self.raw.text() } - pub fn green(&self) -> &GreenNodeData { + pub fn green(&self) -> Cow<'_, GreenNodeData> { self.raw.green() } @@ -243,6 +243,23 @@ impl SyntaxNode { pub fn clone_subtree(&self) -> SyntaxNode { SyntaxNode::from(self.raw.clone_subtree()) } + + pub fn clone_for_update(&self) -> SyntaxNode { + SyntaxNode::from(self.raw.clone_for_update()) + } + + pub fn is_mutable(&self) -> bool { + self.raw.is_mutable() + } + + pub fn detach(&self) { + self.raw.detach() + } + + pub fn splice_children(&self, to_delete: Range, to_insert: Vec>) { + let to_insert = to_insert.into_iter().map(cursor::SyntaxElement::from).collect::>(); + self.raw.splice_children(to_delete, to_insert) + } } impl SyntaxToken { @@ -314,6 +331,10 @@ impl SyntaxToken { pub fn prev_token(&self) -> Option> { self.raw.prev_token().map(SyntaxToken::from) } + + pub fn detach(&self) { + self.raw.detach() + } } impl SyntaxElement { @@ -372,6 +393,12 @@ impl SyntaxElement { NodeOrToken::Token(it) => it.prev_sibling_or_token(), } } + pub fn detach(&self) { + match self { + NodeOrToken::Node(it) => it.detach(), + NodeOrToken::Token(it) => it.detach(), + } + } } #[derive(Debug, Clone)] diff --git a/src/ast.rs b/src/ast.rs index 01f05915..856a9f63 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -39,6 +39,13 @@ pub trait AstNode { fn syntax(&self) -> &SyntaxNode; + fn clone_for_update(&self) -> Self + where + Self: Sized, + { + Self::cast(self.syntax().clone_for_update()).unwrap() + } + fn clone_subtree(&self) -> Self where Self: Sized, diff --git a/src/cursor.rs b/src/cursor.rs index 3ebe023a..cb210eca 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -7,6 +7,12 @@ //! `SyntaxNode`. This allows cursor to provide iteration over both ancestors //! and descendants, as well as a cheep access to absolute offset of the node in //! file. +//! +//! By default `SyntaxNode`s are immutable, but you can get a mutable copy of +//! the tree by calling `clone_for_update`. Mutation is based on interior +//! mutability and doesn't need `&mut`. You can have two `SyntaxNode`s pointing +//! at different parts of the same tree; mutations via the first node will be +//! reflected in the other. // Implementation notes: // @@ -29,14 +35,60 @@ // pointing somewhere in the middle of the tree, then all `NodeData` on the path // from that point towards the root have ref count equal to one. // -// A root `NodeData` owns its green node and is responsible for freeing it. +// `NodeData` which doesn't have a parent (is a root) owns the corresponding +// green node or token, and is responsible for freeing it. +// +// That's mostly it for the immutable subset of the API. Mutation is fun though, +// you'll like it! +// +// Mutability is a run-time property of a tree of `NodeData`. The whole tree is +// either mutable or immutable. `clone_for_update` clones the whole tree of +// `NodeData`s, making it mutable (note that the green tree is re-used). +// +// If the tree is mutable, then all live `NodeData` are additionally liked to +// each other via intrusive liked lists. Specifically, there are two pointers to +// siblings, as well as a pointer to the first child. Note that only live nodes +// are considered. If the user only has `SyntaxNode`s for the first and last +// children of some particular node, then their `NodeData` will point at each +// other. +// +// The links are used to propagate mutations across the tree. Specifically, each +// `NodeData` remembers it's index in parent. When the node is detached from or +// attached to the tree, we need to adjust the indices of all subsequent +// siblings. That's what makes the `for c in node.children() { c.detach() }` +// pattern work despite the apparent iterator invalidation. +// +// This code is encapsulated into the sorted linked list (`sll`) module. +// +// The actual mutation consist of functionally "mutating" (creating a +// structurally shared copy) the green node, and then re-spinning the tree. This +// is a delicate process: `NodeData` point directly to the green nodes, so we +// must make sure that those nodes don't move. Additionally, during mutation a +// node might become or might stop being a root, so we must take care to not +// double free / leak its green node. +// +// Because we can change green nodes using only shared references, handing out +// references into green nodes in the public API would be unsound. We don't do +// that, but we do use such references internally a lot. Additionally, for +// tokens the underlying green token actually is immutable, so we can, and do +// return `&str`. +// +// Invariants [must not leak outside of the module]: +// - Mutability is the property of the whole tree. Intermixing elements that +// differ in mutability is not allowed. +// - Mutability property is persistent. +// - References to the green elements' data are not exposed into public API +// when the tree is mutable. +// - TBD use std::{ + borrow::Cow, cell::Cell, fmt, hash::{Hash, Hasher}, iter, - mem::ManuallyDrop, + mem::{self, ManuallyDrop}, + ops::Range, ptr, slice, }; @@ -44,12 +96,14 @@ use countme::Count; use crate::{ green::{GreenChild, GreenElementRef, GreenNodeData, GreenTokenData, SyntaxKind}, + sll, + utility_types::Delta, Direction, GreenNode, GreenToken, NodeOrToken, SyntaxText, TextRange, TextSize, TokenAtOffset, WalkEvent, }; enum Green { - Node { ptr: ptr::NonNull }, + Node { ptr: Cell> }, Token { ptr: ptr::NonNull }, } @@ -59,10 +113,32 @@ struct NodeData { _c: Count<_SyntaxElement>, rc: Cell, - parent: Option>, - index: u32, + parent: Cell>>, + index: Cell, green: Green, + + /// Invariant: never changes after NodeData is created. + mutable: bool, + /// Absolute offset for immutable nodes, unused for mutable nodes. offset: TextSize, + // The following links only have meaning when `mutable` is true. + first: Cell<*const NodeData>, + /// Invariant: never null if mutable. + next: Cell<*const NodeData>, + /// Invariant: never null if mutable. + prev: Cell<*const NodeData>, +} + +unsafe impl sll::Elem for NodeData { + fn prev(&self) -> &Cell<*const Self> { + &self.prev + } + fn next(&self) -> &Cell<*const Self> { + &self.next + } + fn key(&self) -> &Cell { + &self.index + } } pub type SyntaxElement = NodeOrToken; @@ -114,10 +190,14 @@ impl Drop for SyntaxToken { unsafe fn free(mut data: ptr::NonNull) { loop { debug_assert_eq!(data.as_ref().rc.get(), 0); + debug_assert!(data.as_ref().first.get().is_null()); let node = Box::from_raw(data.as_ptr()); - match node.parent { + match node.parent.take() { Some(parent) => { debug_assert!(parent.as_ref().rc.get() > 0); + if node.mutable { + sll::unlink(&parent.as_ref().first, &*node) + } if parent.as_ref().dec_rc() { data = parent; } else { @@ -125,10 +205,13 @@ unsafe fn free(mut data: ptr::NonNull) { } } None => { - if let Green::Node { ptr } = &node.green { - let _ = GreenNode::from_raw(*ptr); - } else { - unreachable!("a token cannot be a root"); + match &node.green { + Green::Node { ptr } => { + let _ = GreenNode::from_raw(ptr.get()); + } + Green::Token { ptr } => { + let _ = GreenToken::from_raw(*ptr); + } } break; } @@ -143,17 +226,57 @@ impl NodeData { index: u32, offset: TextSize, green: Green, + mutable: bool, ) -> ptr::NonNull { let parent = ManuallyDrop::new(parent); let res = NodeData { _c: Count::new(), rc: Cell::new(1), - parent: parent.as_ref().map(|it| it.ptr), - index, + parent: Cell::new(parent.as_ref().map(|it| it.ptr)), + index: Cell::new(index), green, + + mutable, offset, + first: Cell::new(ptr::null()), + next: Cell::new(ptr::null()), + prev: Cell::new(ptr::null()), }; - unsafe { ptr::NonNull::new_unchecked(Box::into_raw(Box::new(res))) } + unsafe { + if mutable { + let res_ptr: *const NodeData = &res; + match sll::init((*res_ptr).parent().map(|it| &it.first), res_ptr.as_ref().unwrap()) + { + sll::AddToSllResult::AlreadyInSll(node) => { + if cfg!(debug_assertions) { + assert_eq!((*node).index(), (*res_ptr).index()); + match ((*node).green(), (*res_ptr).green()) { + (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { + assert!(ptr::eq(lhs, rhs)) + } + (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => { + assert!(ptr::eq(lhs, rhs)) + } + it => { + panic!("node/token confusion: {:?}", it) + } + } + } + + ManuallyDrop::into_inner(parent); + let res = node as *mut NodeData; + (*res).inc_rc(); + return ptr::NonNull::new_unchecked(res); + } + it => { + let res = Box::into_raw(Box::new(res)); + it.add_to_sll(res); + return ptr::NonNull::new_unchecked(res); + } + } + } + ptr::NonNull::new_unchecked(Box::into_raw(Box::new(res))) + } } #[inline] @@ -175,7 +298,7 @@ impl NodeData { #[inline] fn key(&self) -> (ptr::NonNull<()>, TextSize) { let ptr = match &self.green { - Green::Node { ptr } => ptr.cast(), + Green::Node { ptr } => ptr.get().cast(), Green::Token { ptr } => ptr.cast(), }; (ptr, self.offset()) @@ -191,20 +314,20 @@ impl NodeData { #[inline] fn parent(&self) -> Option<&NodeData> { - self.parent.map(|it| unsafe { &*it.as_ptr() }) + self.parent.get().map(|it| unsafe { &*it.as_ptr() }) } #[inline] fn green(&self) -> GreenElementRef<'_> { match &self.green { - Green::Node { ptr } => GreenElementRef::Node(unsafe { &*ptr.as_ptr() }), - Green::Token { ptr } => GreenElementRef::Token(unsafe { ptr.as_ref() }), + Green::Node { ptr } => GreenElementRef::Node(unsafe { &*ptr.get().as_ptr() }), + Green::Token { ptr } => GreenElementRef::Token(unsafe { &*ptr.as_ref() }), } } #[inline] fn green_siblings(&self) -> slice::Iter<'_, GreenChild> { match &self.parent().map(|it| &it.green) { - Some(Green::Node { ptr }) => unsafe { &*ptr.as_ptr() }.children().raw, + Some(Green::Node { ptr }) => unsafe { &*ptr.get().as_ptr() }.children().raw, Some(Green::Token { .. }) => { debug_assert!(false); [].iter() @@ -214,12 +337,30 @@ impl NodeData { } #[inline] fn index(&self) -> u32 { - self.index + self.index.get() } #[inline] fn offset(&self) -> TextSize { - self.offset + if self.mutable { + self.offset_mut() + } else { + self.offset + } + } + + #[cold] + fn offset_mut(&self) -> TextSize { + let mut res = TextSize::from(0); + + let mut node = self; + while let Some(parent) = node.parent() { + let green = parent.green().into_node().unwrap(); + res += green.children().raw.nth(node.index() as usize).unwrap().rel_offset(); + node = parent; + } + + res } #[inline] @@ -281,13 +422,115 @@ impl NodeData { Some(SyntaxElement::new(child.as_ref(), parent, index as u32, offset)) }) } + + fn detach(&self) { + assert!(self.mutable); + assert!(self.rc.get() > 0); + let parent_ptr = match self.parent.take() { + Some(parent) => parent, + None => return, + }; + + unsafe { + sll::adjust(self, self.index() + 1, Delta::Sub(1)); + let parent = parent_ptr.as_ref(); + sll::unlink(&parent.first, self); + + // Add strong ref to green + match self.green().to_owned() { + NodeOrToken::Node(it) => { + GreenNode::into_raw(it); + } + NodeOrToken::Token(it) => { + GreenToken::into_raw(it); + } + } + + match parent.green() { + NodeOrToken::Node(green) => { + let green = green.remove_child(self.index() as usize); + parent.respine(green) + } + NodeOrToken::Token(_) => unreachable!(), + } + + if parent.dec_rc() { + free(parent_ptr) + } + } + } + fn attach_child(&self, index: usize, child: &NodeData) { + assert!(self.mutable && child.mutable && child.parent().is_none()); + assert!(self.rc.get() > 0 && child.rc.get() > 0); + + unsafe { + child.index.set(index as u32); + child.parent.set(Some(self.into())); + self.inc_rc(); + + if !self.first.get().is_null() { + sll::adjust(&*self.first.get(), index as u32, Delta::Add(1)); + } + + match sll::link(&self.first, child) { + sll::AddToSllResult::AlreadyInSll(_) => { + panic!("Child already in sorted linked list") + } + it => it.add_to_sll(child), + } + + match self.green() { + NodeOrToken::Node(green) => { + // Child is root, so it ownes the green node. Steal it! + let child_green = match &child.green { + Green::Node { ptr } => GreenNode::from_raw(ptr.get()).into(), + Green::Token { ptr } => GreenToken::from_raw(*ptr).into(), + }; + + let green = green.insert_child(index, child_green); + self.respine(green); + } + NodeOrToken::Token(_) => unreachable!(), + } + } + } + unsafe fn respine(&self, mut new_green: GreenNode) { + let mut node = self; + loop { + let old_green = match &node.green { + Green::Node { ptr } => ptr.replace(ptr::NonNull::from(&*new_green)), + Green::Token { .. } => unreachable!(), + }; + match node.parent() { + Some(parent) => match parent.green() { + NodeOrToken::Node(parent_green) => { + new_green = + parent_green.replace_child(node.index() as usize, new_green.into()); + node = parent; + } + _ => unreachable!(), + }, + None => { + mem::forget(new_green); + let _ = GreenNode::from_raw(old_green); + break; + } + } + } + } } impl SyntaxNode { pub fn new_root(green: GreenNode) -> SyntaxNode { let green = GreenNode::into_raw(green); - let green = Green::Node { ptr: green }; - SyntaxNode { ptr: NodeData::new(None, 0, 0.into(), green) } + let green = Green::Node { ptr: Cell::new(green) }; + SyntaxNode { ptr: NodeData::new(None, 0, 0.into(), green, false) } + } + + pub fn new_root_mut(green: GreenNode) -> SyntaxNode { + let green = GreenNode::into_raw(green); + let green = Green::Node { ptr: Cell::new(green) }; + SyntaxNode { ptr: NodeData::new(None, 0, 0.into(), green, true) } } fn new_child( @@ -296,12 +539,28 @@ impl SyntaxNode { index: u32, offset: TextSize, ) -> SyntaxNode { - let green = Green::Node { ptr: green.into() }; - SyntaxNode { ptr: NodeData::new(Some(parent), index, offset, green) } + let mutable = parent.data().mutable; + let green = Green::Node { ptr: Cell::new(green.into()) }; + SyntaxNode { ptr: NodeData::new(Some(parent), index, offset, green, mutable) } + } + + pub fn is_mutable(&self) -> bool { + self.data().mutable + } + + pub fn clone_for_update(&self) -> SyntaxNode { + assert!(!self.data().mutable); + match self.parent() { + Some(parent) => { + let parent = parent.clone_for_update(); + SyntaxNode::new_child(self.green_ref(), parent, self.data().index(), self.offset()) + } + None => SyntaxNode::new_root_mut(self.green_ref().to_owned()), + } } pub fn clone_subtree(&self) -> SyntaxNode { - SyntaxNode::new_root(self.green().to_owned()) + SyntaxNode::new_root(self.green().into()) } #[inline] @@ -348,8 +607,12 @@ impl SyntaxNode { } #[inline] - pub fn green(&self) -> &GreenNodeData { - self.green_ref() + pub fn green(&self) -> Cow<'_, GreenNodeData> { + let green_ref = self.green_ref(); + match self.data().mutable { + false => Cow::Borrowed(green_ref), + true => Cow::Owned(green_ref.to_owned()), + } } #[inline] fn green_ref(&self) -> &GreenNodeData { @@ -551,6 +814,35 @@ impl SyntaxNode { SyntaxElement::new(green, self.clone(), index as u32, self.offset() + rel_offset) }) } + + pub fn splice_children(&self, to_delete: Range, to_insert: Vec) { + assert!(self.data().mutable, "immutable tree: {}", self); + for (i, child) in self.children_with_tokens().enumerate() { + if to_delete.contains(&i) { + child.detach(); + } + } + let mut index = to_delete.start; + for child in to_insert { + self.attach_child(index, child); + index += 1; + } + } + + pub fn detach(&self) { + assert!(self.data().mutable, "immutable tree: {}", self); + self.data().detach() + } + + fn attach_child(&self, index: usize, child: SyntaxElement) { + assert!(self.data().mutable, "immutable tree: {}", self); + child.detach(); + let data = match &child { + NodeOrToken::Node(it) => it.data(), + NodeOrToken::Token(it) => it.data(), + }; + self.data().attach_child(index, data) + } } impl SyntaxToken { @@ -560,8 +852,9 @@ impl SyntaxToken { index: u32, offset: TextSize, ) -> SyntaxToken { + let mutable = parent.data().mutable; let green = Green::Token { ptr: green.into() }; - SyntaxToken { ptr: NodeData::new(Some(parent), index, offset, green) } + SyntaxToken { ptr: NodeData::new(Some(parent), index, offset, green, mutable) } } #[inline] @@ -665,6 +958,11 @@ impl SyntaxToken { .and_then(|element| element.last_token()), } } + + pub fn detach(&self) { + assert!(self.data().mutable, "immutable tree: {}", self); + self.data().detach() + } } impl SyntaxElement { @@ -766,6 +1064,13 @@ impl SyntaxElement { NodeOrToken::Node(node) => node.token_at_offset(offset), } } + + pub fn detach(&self) { + match self { + NodeOrToken::Node(it) => it.detach(), + NodeOrToken::Token(it) => it.detach(), + } + } } // region: impls diff --git a/src/green/element.rs b/src/green/element.rs index d2a2dcfb..2d1ce1f6 100644 --- a/src/green/element.rs +++ b/src/green/element.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::{ green::{GreenNode, GreenToken, SyntaxKind}, GreenNodeData, NodeOrToken, TextSize, @@ -29,6 +31,13 @@ impl From for GreenElement { } } +impl From> for GreenElement { + #[inline] + fn from(cow: Cow<'_, GreenNodeData>) -> Self { + NodeOrToken::Node(cow.into_owned()) + } +} + impl<'a> From<&'a GreenToken> for GreenElementRef<'a> { #[inline] fn from(token: &'a GreenToken) -> GreenElementRef<'a> { diff --git a/src/green/node.rs b/src/green/node.rs index c2eddf0b..7c9e68f9 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -1,5 +1,5 @@ use std::{ - borrow::Borrow, + borrow::{Borrow, Cow}, fmt, iter::{self, FusedIterator}, mem::{self, ManuallyDrop}, @@ -71,6 +71,13 @@ impl Borrow for GreenNode { } } +impl From> for GreenNode { + #[inline] + fn from(cow: Cow<'_, GreenNodeData>) -> Self { + cow.into_owned() + } +} + impl fmt::Debug for GreenNodeData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GreenNode") diff --git a/src/green/token.rs b/src/green/token.rs index fcc5a112..1a4548a4 100644 --- a/src/green/token.rs +++ b/src/green/token.rs @@ -117,7 +117,14 @@ impl GreenToken { GreenToken { ptr } } #[inline] - unsafe fn from_raw(ptr: ptr::NonNull) -> GreenToken { + pub(crate) fn into_raw(this: GreenToken) -> ptr::NonNull { + let green = ManuallyDrop::new(this); + let green: &GreenTokenData = &*green; + ptr::NonNull::from(&*green) + } + + #[inline] + pub(crate) unsafe fn from_raw(ptr: ptr::NonNull) -> GreenToken { let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); let arc = mem::transmute::, ThinArc>(arc); GreenToken { ptr: arc } diff --git a/src/lib.rs b/src/lib.rs index 70e38aaa..bb8f30d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ mod utility_types; mod cow_mut; #[allow(unsafe_code)] +mod sll; +#[allow(unsafe_code)] mod arc; #[cfg(feature = "serde1")] mod serde_impls; diff --git a/src/sll.rs b/src/sll.rs new file mode 100644 index 00000000..87d2f1f3 --- /dev/null +++ b/src/sll.rs @@ -0,0 +1,129 @@ +//! Sorted Linked List + +use std::{cell::Cell, cmp::Ordering, ptr}; + +use crate::utility_types::Delta; +pub(crate) unsafe trait Elem { + fn prev(&self) -> &Cell<*const Self>; + fn next(&self) -> &Cell<*const Self>; + fn key(&self) -> &Cell; +} + +pub(crate) enum AddToSllResult<'a, E: Elem> { + NoHead, + EmptyHead(&'a Cell<*const E>), + SmallerThanHead(&'a Cell<*const E>), + SmallerThanNotHead(*const E), + AlreadyInSll(*const E), +} + +impl<'a, E: Elem> AddToSllResult<'a, E> { + pub(crate) fn add_to_sll(&self, elem_ptr: *const E) { + unsafe { + (*elem_ptr).prev().set(elem_ptr); + (*elem_ptr).next().set(elem_ptr); + + match self { + // Case 1: empty head, replace it. + AddToSllResult::EmptyHead(head) => head.set(elem_ptr), + + // Case 2: we are smaller than the head, replace it. + AddToSllResult::SmallerThanHead(head) => { + let old_head = head.get(); + let prev = (*old_head).prev().replace(elem_ptr); + (*prev).next().set(elem_ptr); + (*elem_ptr).next().set(old_head); + (*elem_ptr).prev().set(prev); + head.set(elem_ptr); + } + + // Case 3: insert in place found by looping + AddToSllResult::SmallerThanNotHead(curr) => { + let next = (**curr).next().replace(elem_ptr); + (*next).prev().set(elem_ptr); + (*elem_ptr).prev().set(*curr); + (*elem_ptr).next().set(next); + } + AddToSllResult::NoHead | AddToSllResult::AlreadyInSll(_) => (), + } + } + } +} + +#[cold] +pub(crate) fn init<'a, E: Elem>( + head: Option<&'a Cell<*const E>>, + elem: &E, +) -> AddToSllResult<'a, E> { + if let Some(head) = head { + link(head, elem) + } else { + AddToSllResult::NoHead + } +} + +#[cold] +pub(crate) fn unlink(head: &Cell<*const E>, elem: &E) { + debug_assert!(!head.get().is_null(), "invalid linked list head"); + + let elem_ptr: *const E = elem; + + let prev = elem.prev().replace(elem_ptr); + let next = elem.next().replace(elem_ptr); + unsafe { + debug_assert_eq!((*prev).next().get(), elem_ptr, "invalid linked list links"); + debug_assert_eq!((*next).prev().get(), elem_ptr, "invalid linked list links"); + (*prev).next().set(next); + (*next).prev().set(prev); + } + + if head.get() == elem_ptr { + head.set(if next == elem_ptr { ptr::null() } else { next }) + } +} + +#[cold] +pub(crate) fn link<'a, E: Elem>(head: &'a Cell<*const E>, elem: &E) -> AddToSllResult<'a, E> { + unsafe { + let old_head = head.get(); + // Case 1: empty head, replace it. + if old_head.is_null() { + return AddToSllResult::EmptyHead(head); + } + + // Case 2: we are smaller than the head, replace it. + if elem.key() < (*old_head).key() { + return AddToSllResult::SmallerThanHead(head); + } + + // Case 3: loop *backward* until we find insertion place. Because of + // Case 2, we can't loop beyond the head. + let mut curr = (*old_head).prev().get(); + loop { + match (*curr).key().cmp(elem.key()) { + Ordering::Less => return AddToSllResult::SmallerThanNotHead(curr), + Ordering::Equal => return AddToSllResult::AlreadyInSll(curr), + Ordering::Greater => curr = (*curr).prev().get(), + } + } + } +} + +pub(crate) fn adjust(elem: &E, from: u32, by: Delta) { + let elem_ptr: *const E = elem; + + unsafe { + let mut curr = elem_ptr; + loop { + let mut key = (*curr).key().get(); + if key >= from { + key += by; + (*curr).key().set(key); + } + curr = (*curr).next().get(); + if curr == elem_ptr { + break; + } + } + } +} diff --git a/src/utility_types.rs b/src/utility_types.rs index c923e618..817add72 100644 --- a/src/utility_types.rs +++ b/src/utility_types.rs @@ -1,4 +1,8 @@ -use std::{fmt, ops::Deref}; +use std::{ + fmt, + ops::{AddAssign, Deref}, +}; +use text_size::TextSize; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum NodeOrToken { @@ -152,3 +156,25 @@ macro_rules! _static_assert { } pub(crate) use _static_assert as static_assert; + +#[derive(Copy, Clone, Debug)] +pub(crate) enum Delta { + Add(T), + Sub(T), +} + +// This won't be coherent :-( +// impl AddAssign> for T +macro_rules! impls { + ($($ty:ident)*) => {$( + impl AddAssign> for $ty { + fn add_assign(&mut self, rhs: Delta<$ty>) { + match rhs { + Delta::Add(amt) => *self += amt, + Delta::Sub(amt) => *self -= amt, + } + } + } + )*}; +} +impls!(u32 TextSize);