From 4ae7068a4f44d43a19c15960be1c2a17e2dcd0d3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 Jan 2021 15:43:46 +0300 Subject: [PATCH 1/7] Prep next major version One tiny step todards https://github.com/rust-analyzer/rust-analyzer/issues/6857 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8c8cd83..23d130d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rowan" -version = "0.10.0" +version = "0.11.0-pre.1" authors = ["Aleksey Kladov "] repository = "https://github.com/rust-analyzer/rowan" license = "MIT OR Apache-2.0" From feedf166dd30be014f107ba4dce51bcf08073f10 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 Jan 2021 16:07:02 +0300 Subject: [PATCH 2/7] Introduce GreenChild --- src/green/node.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/green/node.rs b/src/green/node.rs index 5e2687a..212220d 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -1,11 +1,11 @@ -use std::{iter::FusedIterator, slice, sync::Arc}; +use std::{iter::FusedIterator, mem, slice, sync::Arc}; use erasable::Thin; use slice_dst::SliceWithHeader; use crate::{ green::{GreenElement, GreenElementRef, PackedGreenElement, SyntaxKind}, - TextSize, + GreenToken, TextSize, }; #[repr(align(2))] // NB: this is an at-least annotation @@ -23,6 +23,23 @@ pub struct GreenNode { pub(super) data: Thin>>, } +enum GreenChild { + Node { + // offset: TextSize, + node: GreenNode + }, + Token { + // offset: TextSize, + token: GreenToken + }, +} + +#[cfg(target_pointer_width = "64")] +const _: () = { + let cond = mem::size_of::() == mem::size_of::() * 2; + [()][(!cond) as usize] +}; + impl GreenNode { /// Creates new Node. #[inline] From 6a61abd3a0302308f7dfe952678af04380f12dcd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 Jan 2021 16:22:08 +0300 Subject: [PATCH 3/7] Add triomphe As we'll put offsets into children array, we won't be able to use the union trick. So lets use weakless arc instead! --- Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23d130d..31d599d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rowan" -version = "0.11.0-pre.1" +version = "0.10.1" authors = ["Aleksey Kladov "] repository = "https://github.com/rust-analyzer/rowan" license = "MIT OR Apache-2.0" @@ -10,10 +10,12 @@ edition = "2018" [dependencies] erasable = "1.2.1" rustc-hash = "1.0.1" -serde = { version = "1.0.89", optional = true, default-features = false } slice-dst = "1.4.1" smol_str = "0.1.10" text-size = "1.0.0" +triomphe = "0.1.1" + +serde = { version = "1.0.89", optional = true, default-features = false } [dev-dependencies] m_lexer = "0.0.4" From cfc98d30f984d4ceb3623da0418090b13eede467 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 Jan 2021 18:51:25 +0300 Subject: [PATCH 4/7] Prepare to store offset_in_parent in green nodes --- Cargo.toml | 2 - src/green.rs | 4 +- src/green/element.rs | 141 ------------------------------------------- src/green/node.rs | 86 +++++++++++++++++--------- src/green/token.rs | 77 ++++------------------- 5 files changed, 70 insertions(+), 240 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31d599d..eaa5b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,7 @@ description = "Library for generic lossless syntax trees" edition = "2018" [dependencies] -erasable = "1.2.1" rustc-hash = "1.0.1" -slice-dst = "1.4.1" smol_str = "0.1.10" text-size = "1.0.0" triomphe = "0.1.1" diff --git a/src/green.rs b/src/green.rs index 6b004ad..740d015 100644 --- a/src/green.rs +++ b/src/green.rs @@ -3,8 +3,8 @@ mod token; mod element; mod builder; +use self::element::GreenElement; pub(crate) use self::element::GreenElementRef; -use self::element::{GreenElement, PackedGreenElement}; pub use self::{ builder::{Checkpoint, GreenNodeBuilder, NodeCache}, @@ -26,7 +26,6 @@ mod tests { f::(); f::(); f::(); - f::(); } #[test] @@ -36,6 +35,5 @@ mod tests { eprintln!("GreenNode {}", size_of::()); eprintln!("GreenToken {}", size_of::()); eprintln!("GreenElement {}", size_of::()); - eprintln!("PackedGreenElement {}", size_of::()); } } diff --git a/src/green/element.rs b/src/green/element.rs index 67d23f3..db314f0 100644 --- a/src/green/element.rs +++ b/src/green/element.rs @@ -1,7 +1,3 @@ -use std::{fmt, hash, mem}; - -use erasable::ErasedPtr; - use crate::{ green::{GreenNode, GreenToken, SyntaxKind}, NodeOrToken, TextSize, @@ -10,11 +6,6 @@ use crate::{ pub(super) type GreenElement = NodeOrToken; pub(crate) type GreenElementRef<'a> = NodeOrToken<&'a GreenNode, &'a GreenToken>; -#[repr(transparent)] -pub(super) struct PackedGreenElement { - ptr: ErasedPtr, -} - impl From for GreenElement { #[inline] fn from(node: GreenNode) -> GreenElement { @@ -29,13 +20,6 @@ impl<'a> From<&'a GreenNode> for GreenElementRef<'a> { } } -impl From for PackedGreenElement { - #[inline] - fn from(node: GreenNode) -> PackedGreenElement { - unsafe { mem::transmute(node) } - } -} - impl From for GreenElement { #[inline] fn from(token: GreenToken) -> GreenElement { @@ -50,13 +34,6 @@ impl<'a> From<&'a GreenToken> for GreenElementRef<'a> { } } -impl From for PackedGreenElement { - #[inline] - fn from(token: GreenToken) -> PackedGreenElement { - unsafe { mem::transmute(token) } - } -} - impl GreenElement { /// Returns kind of this element. #[inline] @@ -90,121 +67,3 @@ impl GreenElementRef<'_> { } } } - -impl From for PackedGreenElement { - fn from(element: GreenElement) -> Self { - match element { - NodeOrToken::Node(node) => node.into(), - NodeOrToken::Token(token) => token.into(), - } - } -} - -impl From for GreenElement { - fn from(element: PackedGreenElement) -> Self { - if element.is_node() { - NodeOrToken::Node(element.into_node().unwrap()) - } else { - NodeOrToken::Token(element.into_token().unwrap()) - } - } -} - -impl PackedGreenElement { - fn is_node(&self) -> bool { - self.ptr.as_ptr() as usize & 1 == 0 - } - - pub(crate) fn as_node(&self) -> Option<&GreenNode> { - if self.is_node() { - unsafe { Some(&*(&self.ptr as *const ErasedPtr as *const GreenNode)) } - } else { - None - } - } - - pub(crate) fn into_node(self) -> Option { - if self.is_node() { - unsafe { Some(mem::transmute(self)) } - } else { - None - } - } - - pub(crate) fn as_token(&self) -> Option<&GreenToken> { - if !self.is_node() { - unsafe { Some(&*(&self.ptr as *const ErasedPtr as *const GreenToken)) } - } else { - None - } - } - - pub(crate) fn into_token(self) -> Option { - if !self.is_node() { - unsafe { Some(mem::transmute(self)) } - } else { - None - } - } - - pub(crate) fn as_ref(&self) -> GreenElementRef<'_> { - if self.is_node() { - NodeOrToken::Node(self.as_node().unwrap()) - } else { - NodeOrToken::Token(self.as_token().unwrap()) - } - } -} - -impl fmt::Debug for PackedGreenElement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_node() { - self.as_node().unwrap().fmt(f) - } else { - self.as_token().unwrap().fmt(f) - } - } -} - -impl Eq for PackedGreenElement {} -impl PartialEq for PackedGreenElement { - fn eq(&self, other: &Self) -> bool { - self.as_node() == other.as_node() && self.as_token() == other.as_token() - } -} - -impl hash::Hash for PackedGreenElement { - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - if self.is_node() { - self.as_node().unwrap().hash(state) - } else { - self.as_token().unwrap().hash(state) - } - } -} - -impl Drop for PackedGreenElement { - fn drop(&mut self) { - if self.is_node() { - PackedGreenElement { ptr: self.ptr }.into_node(); - } else { - PackedGreenElement { ptr: self.ptr }.into_token(); - } - } -} - -unsafe impl Send for PackedGreenElement -where - GreenToken: Send, - GreenNode: Send, -{ -} -unsafe impl Sync for PackedGreenElement -where - GreenToken: Sync, - GreenNode: Sync, -{ -} diff --git a/src/green/node.rs b/src/green/node.rs index 212220d..5af05ce 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -1,14 +1,12 @@ -use std::{iter::FusedIterator, mem, slice, sync::Arc}; +use std::{ffi::c_void, fmt, iter::FusedIterator, mem, slice}; -use erasable::Thin; -use slice_dst::SliceWithHeader; +use triomphe::{Arc, ThinArc}; use crate::{ - green::{GreenElement, GreenElementRef, PackedGreenElement, SyntaxKind}, - GreenToken, TextSize, + green::{GreenElement, GreenElementRef, SyntaxKind}, + GreenToken, NodeOrToken, TextSize, }; -#[repr(align(2))] // NB: this is an at-least annotation #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(super) struct GreenNodeHead { kind: SyntaxKind, @@ -17,23 +15,51 @@ pub(super) struct GreenNodeHead { /// Internal node in the immutable tree. /// It has other nodes and tokens as children. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct GreenNode { - pub(super) data: Thin>>, + data: ThinArc, +} + +impl fmt::Debug for GreenNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GreenNode") + .field("kind", &self.kind()) + .field("text_len", &self.text_len()) + .field("n_children", &self.children().len()) + .finish() + } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum GreenChild { Node { // offset: TextSize, - node: GreenNode + node: GreenNode, }, Token { // offset: TextSize, - token: GreenToken + token: GreenToken, }, } +impl From for GreenChild { + fn from(e: GreenElement) -> Self { + match e { + NodeOrToken::Node(node) => GreenChild::Node { node }, + NodeOrToken::Token(token) => GreenChild::Token { token }, + } + } +} + +impl GreenChild { + fn as_ref(&self) -> GreenElementRef { + match self { + GreenChild::Node { node } => NodeOrToken::Node(node), + GreenChild::Token { token } => NodeOrToken::Token(token), + } + } +} + #[cfg(target_pointer_width = "64")] const _: () = { let cond = mem::size_of::() == mem::size_of::() * 2; @@ -49,30 +75,33 @@ impl GreenNode { I::IntoIter: ExactSizeIterator, { let mut text_len: TextSize = 0.into(); - let children = children - .into_iter() - .inspect(|it| text_len += it.text_len()) - .map(PackedGreenElement::from); - let mut data: Arc<_> = - SliceWithHeader::new(GreenNodeHead { kind, text_len: 0.into() }, children); + let children = + children.into_iter().inspect(|it| text_len += it.text_len()).map(GreenChild::from); + + let data = + ThinArc::from_header_and_iter(GreenNodeHead { kind, text_len: 0.into() }, children); // XXX: fixup `text_len` after construction, because we can't iterate // `children` twice. - Arc::get_mut(&mut data).unwrap().header.text_len = text_len; + let data = { + let mut data = Arc::from_thin(data); + Arc::get_mut(&mut data).unwrap().header.header.text_len = text_len; + Arc::into_thin(data) + }; - GreenNode { data: data.into() } + GreenNode { data } } /// Kind of this node. #[inline] pub fn kind(&self) -> SyntaxKind { - self.data.header.kind + self.data.header.header.kind } /// Returns the length of the text covered by this node. #[inline] pub fn text_len(&self) -> TextSize { - self.data.header.text_len + self.data.header.header.text_len } /// Children of this node. @@ -81,15 +110,14 @@ impl GreenNode { Children { inner: self.data.slice.iter() } } - pub(crate) fn ptr(&self) -> *const u8 { - let r: &SliceWithHeader<_, _> = &*self.data; - r as *const _ as _ + pub fn ptr(&self) -> *const c_void { + self.data.heap_ptr() } } #[derive(Debug, Clone)] pub struct Children<'a> { - inner: slice::Iter<'a, PackedGreenElement>, + inner: slice::Iter<'a, GreenChild>, } // NB: forward everything stable that iter::Slice specializes as of Rust 1.39.0 @@ -105,7 +133,7 @@ impl<'a> Iterator for Children<'a> { #[inline] fn next(&mut self) -> Option> { - self.inner.next().map(PackedGreenElement::as_ref) + self.inner.next().map(GreenChild::as_ref) } #[inline] @@ -123,7 +151,7 @@ impl<'a> Iterator for Children<'a> { #[inline] fn nth(&mut self, n: usize) -> Option { - self.inner.nth(n).map(PackedGreenElement::as_ref) + self.inner.nth(n).map(GreenChild::as_ref) } #[inline] @@ -150,12 +178,12 @@ impl<'a> Iterator for Children<'a> { impl<'a> DoubleEndedIterator for Children<'a> { #[inline] fn next_back(&mut self) -> Option { - self.inner.next_back().map(PackedGreenElement::as_ref) + self.inner.next_back().map(GreenChild::as_ref) } #[inline] fn nth_back(&mut self, n: usize) -> Option { - self.inner.nth_back(n).map(PackedGreenElement::as_ref) + self.inner.nth_back(n).map(GreenChild::as_ref) } #[inline] diff --git a/src/green/token.rs b/src/green/token.rs index be0fefd..0284e3c 100644 --- a/src/green/token.rs +++ b/src/green/token.rs @@ -1,66 +1,49 @@ -use std::{convert::TryFrom, fmt, hash, mem::ManuallyDrop, ptr, sync::Arc}; +use std::fmt; + +use triomphe::Arc; use crate::{green::SyntaxKind, SmolStr, TextSize}; -#[repr(align(2))] // NB: this is an at-least annotation -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] struct GreenTokenData { kind: SyntaxKind, text: SmolStr, } /// Leaf node in the immutable tree. -#[repr(transparent)] +#[derive(PartialEq, Eq, Hash, Clone)] pub struct GreenToken { - ptr: ptr::NonNull, + data: Arc, } -unsafe impl Send for GreenToken {} // where GreenTokenData: Send + Sync -unsafe impl Sync for GreenToken {} // where GreenTokenData: Send + Sync - impl GreenToken { - fn add_tag(ptr: ptr::NonNull) -> ptr::NonNull { - unsafe { - let ptr = ((ptr.as_ptr() as usize) | 1) as *mut GreenTokenData; - ptr::NonNull::new_unchecked(ptr) - } - } - - fn remove_tag(ptr: ptr::NonNull) -> ptr::NonNull { - unsafe { - let ptr = ((ptr.as_ptr() as usize) & !1) as *mut GreenTokenData; - ptr::NonNull::new_unchecked(ptr) - } - } - fn data(&self) -> &GreenTokenData { - unsafe { &*Self::remove_tag(self.ptr).as_ptr() } + &*self.data } /// Creates new Token. #[inline] pub fn new(kind: SyntaxKind, text: SmolStr) -> GreenToken { - let ptr = Arc::into_raw(Arc::new(GreenTokenData { kind, text })); - let ptr = ptr::NonNull::new(ptr as *mut _).unwrap(); - GreenToken { ptr: Self::add_tag(ptr) } + let data = Arc::new(GreenTokenData { kind, text }); + GreenToken { data } } /// Kind of this Token. #[inline] pub fn kind(&self) -> SyntaxKind { - self.data().kind + self.data.kind } /// Text of this Token. #[inline] pub fn text(&self) -> &SmolStr { - &self.data().text + &self.data.text } /// Returns the length of the text covered by this token. #[inline] pub fn text_len(&self) -> TextSize { - TextSize::try_from(self.text().len()).unwrap() + TextSize::of(self.text().as_str()) } } @@ -70,39 +53,3 @@ impl fmt::Debug for GreenToken { f.debug_struct("GreenToken").field("kind", &data.kind).field("text", &data.text).finish() } } - -impl Clone for GreenToken { - fn clone(&self) -> Self { - let ptr = Self::remove_tag(self.ptr); - let ptr = unsafe { - let arc = ManuallyDrop::new(Arc::from_raw(ptr.as_ptr())); - Arc::into_raw(Arc::clone(&arc)) - }; - let ptr = ptr::NonNull::new(ptr as *mut _).unwrap(); - GreenToken { ptr: Self::add_tag(ptr) } - } -} - -impl Eq for GreenToken {} -impl PartialEq for GreenToken { - fn eq(&self, other: &Self) -> bool { - self.data() == other.data() - } -} - -impl hash::Hash for GreenToken { - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - self.data().hash(state) - } -} - -impl Drop for GreenToken { - fn drop(&mut self) { - unsafe { - Arc::from_raw(Self::remove_tag(self.ptr).as_ptr()); - } - } -} From 41bc29ce6c8b3b9ad2242706c325fa130f83c93f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 Jan 2021 19:28:14 +0300 Subject: [PATCH 5/7] Store relative offsets in green node --- src/cursor.rs | 10 +--------- src/green/node.rs | 49 +++++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/cursor.rs b/src/cursor.rs index db6901b..2cc5922 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -217,15 +217,7 @@ impl SyntaxNode { match self.0.kind.as_child() { None => replacement, Some((parent, me, _offset)) => { - let mut replacement = Some(replacement); - let children = parent.green().children().enumerate().map(|(i, child)| { - if i as u32 == me { - replacement.take().unwrap().into() - } else { - child.cloned() - } - }); - let new_parent = GreenNode::new(parent.kind(), children); + let new_parent = parent.green().replace_child(me as usize, replacement.into()); parent.replace_with(new_parent) } } diff --git a/src/green/node.rs b/src/green/node.rs index 5af05ce..cf710f9 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -32,38 +32,23 @@ impl fmt::Debug for GreenNode { #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum GreenChild { - Node { - // offset: TextSize, - node: GreenNode, - }, - Token { - // offset: TextSize, - token: GreenToken, - }, -} - -impl From for GreenChild { - fn from(e: GreenElement) -> Self { - match e { - NodeOrToken::Node(node) => GreenChild::Node { node }, - NodeOrToken::Token(token) => GreenChild::Token { token }, - } - } + Node { offset_in_parent: TextSize, node: GreenNode }, + Token { offset_in_parent: TextSize, token: GreenToken }, } impl GreenChild { fn as_ref(&self) -> GreenElementRef { match self { - GreenChild::Node { node } => NodeOrToken::Node(node), - GreenChild::Token { token } => NodeOrToken::Token(token), + GreenChild::Node { node, .. } => NodeOrToken::Node(node), + GreenChild::Token { token, .. } => NodeOrToken::Token(token), } } } #[cfg(target_pointer_width = "64")] -const _: () = { +const _: i32 = { let cond = mem::size_of::() == mem::size_of::() * 2; - [()][(!cond) as usize] + 0 / cond as i32 }; impl GreenNode { @@ -75,8 +60,14 @@ impl GreenNode { I::IntoIter: ExactSizeIterator, { let mut text_len: TextSize = 0.into(); - let children = - children.into_iter().inspect(|it| text_len += it.text_len()).map(GreenChild::from); + let children = children.into_iter().map(|el| { + let offset_in_parent = text_len; + text_len += el.text_len(); + match el { + NodeOrToken::Node(node) => GreenChild::Node { offset_in_parent, node }, + NodeOrToken::Token(token) => GreenChild::Token { offset_in_parent, token }, + } + }); let data = ThinArc::from_header_and_iter(GreenNodeHead { kind, text_len: 0.into() }, children); @@ -113,6 +104,18 @@ impl GreenNode { pub fn ptr(&self) -> *const c_void { self.data.heap_ptr() } + + pub(crate) fn replace_child(&self, idx: usize, new_child: GreenElement) -> GreenNode { + let mut replacement = Some(new_child); + let children = self.children().enumerate().map(|(i, child)| { + if i == idx { + replacement.take().unwrap() + } else { + child.cloned() + } + }); + GreenNode::new(self.kind(), children) + } } #[derive(Debug, Clone)] From 78587c277bd2653734a120165c99a4019ed7b78d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 13 Jan 2021 19:31:31 +0300 Subject: [PATCH 6/7] Move doc comments to the API module --- src/api.rs | 20 ++++++++++++++++++++ src/cursor.rs | 21 +-------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/api.rs b/src/api.rs index 9fb1390..cf605f8 100644 --- a/src/api.rs +++ b/src/api.rs @@ -149,6 +149,9 @@ impl SyntaxNode { pub fn new_root(green: GreenNode) -> SyntaxNode { SyntaxNode::from(cursor::SyntaxNode::new_root(green)) } + /// Returns a green tree, equal to the green tree this node + /// belongs two, except with this node substitute. The complexity + /// of operation is proportional to the depth of the tree pub fn replace_with(&self, replacement: GreenNode) -> GreenNode { self.raw.replace_with(replacement) } @@ -217,10 +220,12 @@ impl SyntaxNode { self.raw.prev_sibling_or_token().map(NodeOrToken::from) } + /// Return the leftmost token in the subtree of this node. pub fn first_token(&self) -> Option> { self.raw.first_token().map(SyntaxToken::from) } + /// Return the rightmost token in the subtree of this node. pub fn last_token(&self) -> Option> { self.raw.last_token().map(SyntaxToken::from) } @@ -244,24 +249,37 @@ impl SyntaxNode { self.raw.descendants_with_tokens().map(NodeOrToken::from) } + /// Traverse the subtree rooted at the current node (including the current + /// node) in preorder, excluding tokens. pub fn preorder(&self) -> impl Iterator>> { self.raw.preorder().map(|event| event.map(SyntaxNode::from)) } + /// Traverse the subtree rooted at the current node (including the current + /// node) in preorder, including tokens. pub fn preorder_with_tokens(&self) -> impl Iterator>> { self.raw.preorder_with_tokens().map(|event| event.map(NodeOrToken::from)) } + /// Find a token in the subtree corresponding to this node, which covers the offset. + /// Precondition: offset must be withing node's range. pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { self.raw.token_at_offset(offset).map(SyntaxToken::from) } + /// Return the deepest node or token in the current subtree that fully + /// contains the range. If the range is empty and is contained in two leaf + /// nodes, either one can be returned. Precondition: range must be contained + /// withing the current node pub fn covering_element(&self, range: TextRange) -> SyntaxElement { NodeOrToken::from(self.raw.covering_element(range)) } } impl SyntaxToken { + /// Returns a green tree, equal to the green tree this token + /// belongs two, except with this token substitute. The complexity + /// of operation is proportional to the depth of the tree pub fn replace_with(&self, new_token: GreenToken) -> GreenNode { self.raw.replace_with(new_token) } @@ -305,10 +323,12 @@ impl SyntaxToken { self.raw.siblings_with_tokens(direction).map(SyntaxElement::from) } + /// Next token in the tree (i.e, not necessary a sibling). pub fn next_token(&self) -> Option> { self.raw.next_token().map(SyntaxToken::from) } + /// Previous token in the tree (i.e, not necessary a sibling). pub fn prev_token(&self) -> Option> { self.raw.prev_token().map(SyntaxToken::from) } diff --git a/src/cursor.rs b/src/cursor.rs index 2cc5922..506fca5 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -209,9 +209,6 @@ impl SyntaxNode { SyntaxNode::new(data) } - /// Returns a green tree, equal to the green tree this node - /// belongs two, except with this node substitute. The complexity - /// of operation is proportional to the depth of the tree pub fn replace_with(&self, replacement: GreenNode) -> GreenNode { assert_eq!(self.kind(), replacement.kind()); match self.0.kind.as_child() { @@ -334,13 +331,11 @@ impl SyntaxNode { Some(SyntaxElement::new(element, parent.clone(), index as u32, offset)) } - /// Return the leftmost token in the subtree of this node #[inline] pub fn first_token(&self) -> Option { self.first_child_or_token()?.first_token() } - /// Return the rightmost token in the subtree of this node #[inline] pub fn last_token(&self) -> Option { self.last_child_or_token()?.last_token() @@ -378,8 +373,6 @@ impl SyntaxNode { }) } - /// Traverse the subtree rooted at the current node (including the current - /// node) in preorder, excluding tokens. #[inline] pub fn preorder(&self) -> impl Iterator> { let this = self.clone(); @@ -403,8 +396,6 @@ impl SyntaxNode { }) } - /// Traverse the subtree rooted at the current node (including the current - /// node) in preorder, including tokens. #[inline] pub fn preorder_with_tokens<'a>(&'a self) -> impl Iterator> { let start: SyntaxElement = self.clone().into(); @@ -431,8 +422,6 @@ impl SyntaxNode { }) } - /// Find a token in the subtree corresponding to this node, which covers the offset. - /// Precondition: offset must be withing node's range. pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset { // TODO: this could be faster if we first drill-down to node, and only // then switch to token search. We should also replace explicit @@ -470,10 +459,6 @@ impl SyntaxNode { } } - /// Return the deepest node or token in the current subtree that fully - /// contains the range. If the range is empty and is contained in two leaf - /// nodes, either one can be returned. Precondition: range must be contained - /// withing the current node pub fn covering_element(&self, range: TextRange) -> SyntaxElement { let mut res: SyntaxElement = self.clone().into(); loop { @@ -504,9 +489,6 @@ impl SyntaxToken { SyntaxToken { parent, index, offset } } - /// Returns a green tree, equal to the green tree this token - /// belongs two, except with this token substitute. The complexity - /// of operation is proportional to the depth of the tree pub fn replace_with(&self, replacement: GreenToken) -> GreenNode { assert_eq!(self.kind(), replacement.kind()); let mut replacement = Some(replacement); @@ -580,7 +562,6 @@ impl SyntaxToken { }) } - /// Next token in the tree (i.e, not necessary a sibling) pub fn next_token(&self) -> Option { match self.next_sibling_or_token() { Some(element) => element.first_token(), @@ -591,7 +572,7 @@ impl SyntaxToken { .and_then(|element| element.first_token()), } } - /// Previous token in the tree (i.e, not necessary a sibling) + pub fn prev_token(&self) -> Option { match self.prev_sibling_or_token() { Some(element) => element.last_token(), From 52af0dbc680bb7373e83cb90a9617208400075d0 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 15 Jan 2021 20:06:11 +0300 Subject: [PATCH 7/7] API to binary search child by range --- Cargo.toml | 2 +- src/api.rs | 4 ++++ src/cursor.rs | 29 ++++++++++++++++++++--------- src/green/node.rs | 30 +++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eaa5b66..8a93690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] rustc-hash = "1.0.1" smol_str = "0.1.10" -text-size = "1.0.0" +text-size = "1.1.0" triomphe = "0.1.1" serde = { version = "1.0.89", optional = true, default-features = false } diff --git a/src/api.rs b/src/api.rs index cf605f8..16daabe 100644 --- a/src/api.rs +++ b/src/api.rs @@ -274,6 +274,10 @@ impl SyntaxNode { pub fn covering_element(&self, range: TextRange) -> SyntaxElement { NodeOrToken::from(self.raw.covering_element(range)) } + + pub fn child_or_token_at_range(&self, range: TextRange) -> Option> { + self.raw.child_or_token_at_range(range).map(SyntaxElement::from) + } } impl SyntaxToken { diff --git a/src/cursor.rs b/src/cursor.rs index 506fca5..364a5cd 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -470,18 +470,29 @@ impl SyntaxNode { ); res = match &res { NodeOrToken::Token(_) => return res, - NodeOrToken::Node(node) => { - match node - .children_with_tokens() - .find(|child| child.text_range().contains_range(range)) - { - Some(child) => child, - None => return res, - } - } + NodeOrToken::Node(node) => match node.child_or_token_at_range(range) { + Some(it) => it, + None => return res, + }, }; } } + + pub fn child_or_token_at_range(&self, range: TextRange) -> Option { + let start_offset = self.text_range().start(); + let (index, offset, child) = self.green().child_at_range(range - start_offset)?; + let index = index as u32; + let offset = offset + start_offset; + let res: SyntaxElement = match child { + NodeOrToken::Node(node) => { + let data = + NodeData::new(Kind::Child { parent: self.clone(), index, offset }, node.into()); + SyntaxNode::new(data).into() + } + NodeOrToken::Token(_token) => SyntaxToken::new(self.clone(), index, offset).into(), + }; + Some(res) + } } impl SyntaxToken { diff --git a/src/green/node.rs b/src/green/node.rs index cf710f9..74b3a6e 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -4,7 +4,7 @@ use triomphe::{Arc, ThinArc}; use crate::{ green::{GreenElement, GreenElementRef, SyntaxKind}, - GreenToken, NodeOrToken, TextSize, + GreenToken, NodeOrToken, TextRange, TextSize, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -43,6 +43,16 @@ impl GreenChild { GreenChild::Token { token, .. } => NodeOrToken::Token(token), } } + fn offset_in_parent(&self) -> TextSize { + match self { + GreenChild::Node { offset_in_parent, .. } + | GreenChild::Token { offset_in_parent, .. } => *offset_in_parent, + } + } + fn range_in_parent(&self) -> TextRange { + let len = self.as_ref().text_len(); + TextRange::at(self.offset_in_parent(), len) + } } #[cfg(target_pointer_width = "64")] @@ -101,6 +111,24 @@ impl GreenNode { Children { inner: self.data.slice.iter() } } + pub(crate) fn child_at_range( + &self, + range: TextRange, + ) -> Option<(usize, TextSize, GreenElementRef<'_>)> { + let idx = self + .data + .slice + .binary_search_by(|it| { + let child_range = it.range_in_parent(); + TextRange::ordering(child_range, range) + }) + // XXX: this handles empty ranges + .unwrap_or_else(|it| it.saturating_sub(1)); + let child = + &self.data.slice.get(idx).filter(|it| it.range_in_parent().contains_range(range))?; + Some((idx, child.offset_in_parent(), child.as_ref())) + } + pub fn ptr(&self) -> *const c_void { self.data.heap_ptr() }