summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-14 09:57:52 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-14 09:57:52 +0100
commit2470df05af993e89405c5c86329e08243960641d (patch)
tree0f241650ab94b52eb11c3f43bbec2711234f65ad /src
parentad66fbdfa2d39e39c2a7d7411e27cb61ada2b648 (diff)
Linked syntax node
Diffstat (limited to 'src')
-rw-r--r--src/syntax/ast.rs2
-rw-r--r--src/syntax/kind.rs4
-rw-r--r--src/syntax/linked.rs250
-rw-r--r--src/syntax/mod.rs2
4 files changed, 256 insertions, 2 deletions
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 4db00593..77c788d3 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -1205,7 +1205,7 @@ node! {
}
impl MethodCall {
- /// The expresion to call the method on.
+ /// The expression to call the method on.
pub fn target(&self) -> Expr {
self.0.cast_first_child().expect("method call is missing target")
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index d29808d3..02e972c4 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -299,7 +299,9 @@ pub enum ErrorPos {
impl SyntaxKind {
/// Whether this is trivia.
pub fn is_trivia(&self) -> bool {
- self.is_space() || matches!(self, Self::LineComment | Self::BlockComment)
+ self.is_space()
+ || self.is_error()
+ || matches!(self, Self::LineComment | Self::BlockComment)
}
/// Whether this is a space.
diff --git a/src/syntax/linked.rs b/src/syntax/linked.rs
new file mode 100644
index 00000000..0d9d0c78
--- /dev/null
+++ b/src/syntax/linked.rs
@@ -0,0 +1,250 @@
+use std::fmt::{self, Debug, Formatter};
+use std::ops::{Deref, Range};
+use std::rc::Rc;
+
+use super::{SyntaxKind, SyntaxNode};
+
+/// A syntax node in a context.
+///
+/// Knows its exact offset in the file and provides access to its
+/// children, parent and siblings.
+///
+/// **Note that all sibling and leaf accessors skip over trivia!**
+#[derive(Clone)]
+pub struct LinkedNode<'a> {
+ node: &'a SyntaxNode,
+ parent: Option<Rc<Self>>,
+ index: usize,
+ offset: usize,
+}
+
+impl<'a> LinkedNode<'a> {
+ /// Start a new traversal at the source's root node.
+ pub fn new(root: &'a SyntaxNode) -> Self {
+ Self { node: root, parent: None, index: 0, offset: 0 }
+ }
+
+ /// Get the contained syntax node.
+ pub fn get(&self) -> &'a SyntaxNode {
+ self.node
+ }
+
+ /// The absolute byte offset of the this node in the source file.
+ pub fn offset(&self) -> usize {
+ self.offset
+ }
+
+ /// The byte range of the this node in the source file.
+ pub fn range(&self) -> Range<usize> {
+ self.offset..self.offset + self.node.len()
+ }
+
+ /// Get this node's children.
+ pub fn children(
+ &self,
+ ) -> impl DoubleEndedIterator<Item = LinkedNode<'a>>
+ + ExactSizeIterator<Item = LinkedNode<'a>>
+ + '_ {
+ let parent = Rc::new(self.clone());
+ let mut offset = self.offset;
+ self.node.children().enumerate().map(move |(index, node)| {
+ let child = Self { node, parent: Some(parent.clone()), index, offset };
+ offset += node.len();
+ child
+ })
+ }
+}
+
+/// Access to parents and siblings.
+impl<'a> LinkedNode<'a> {
+ /// Get this node's parent.
+ pub fn parent(&self) -> Option<&Self> {
+ self.parent.as_deref()
+ }
+
+ /// Get the kind of this node's parent.
+ pub fn parent_kind(&self) -> Option<&'a SyntaxKind> {
+ self.parent().map(|parent| parent.node.kind())
+ }
+
+ /// Get the first previous non-trivia sibling node.
+ pub fn prev_sibling(&self) -> Option<Self> {
+ let parent = self.parent()?;
+ let index = self.index.checked_sub(1)?;
+ let node = parent.node.children().nth(index)?;
+ let offset = self.offset - node.len();
+ let prev = Self { node, parent: self.parent.clone(), index, offset };
+ if prev.kind().is_trivia() {
+ prev.prev_sibling()
+ } else {
+ Some(prev)
+ }
+ }
+
+ /// Get the kind of this node's first previous non-trivia sibling.
+ pub fn prev_sibling_kind(&self) -> Option<&'a SyntaxKind> {
+ self.prev_sibling().map(|parent| parent.node.kind())
+ }
+
+ /// Get the next non-trivia sibling node.
+ pub fn next_sibling(&self) -> Option<Self> {
+ let parent = self.parent()?;
+ let index = self.index.checked_add(1)?;
+ let node = parent.node.children().nth(index)?;
+ let offset = self.offset + self.node.len();
+ let next = Self { node, parent: self.parent.clone(), index, offset };
+ if next.kind().is_trivia() {
+ next.next_sibling()
+ } else {
+ Some(next)
+ }
+ }
+
+ /// Get the kind of this node's next non-trivia sibling.
+ pub fn next_sibling_kind(&self) -> Option<&'a SyntaxKind> {
+ self.next_sibling().map(|parent| parent.node.kind())
+ }
+}
+
+/// Access to leafs.
+impl<'a> LinkedNode<'a> {
+ /// Get the rightmost non-trivia leaf before this node.
+ pub fn prev_leaf(&self) -> Option<Self> {
+ let mut node = self.clone();
+ while let Some(prev) = node.prev_sibling() {
+ if let Some(leaf) = prev.rightmost_leaf() {
+ return Some(leaf);
+ }
+ node = prev;
+ }
+ self.parent()?.prev_leaf()
+ }
+
+ /// Find the leftmost contained non-trivia leaf.
+ pub fn leftmost_leaf(&self) -> Option<Self> {
+ if self.is_leaf() && !self.kind().is_trivia() && !self.kind().is_error() {
+ return Some(self.clone());
+ }
+
+ for child in self.children() {
+ if let Some(leaf) = child.leftmost_leaf() {
+ return Some(leaf);
+ }
+ }
+
+ None
+ }
+
+ /// Get the leaf at the specified cursor position.
+ pub fn leaf_at(&self, cursor: usize) -> Option<Self> {
+ if self.node.children().len() == 0 && cursor <= self.offset + self.len() {
+ return Some(self.clone());
+ }
+
+ let mut offset = self.offset;
+ let count = self.node.children().len();
+ for (i, child) in self.children().enumerate() {
+ let len = child.len();
+ if (offset < cursor && cursor <= offset + len)
+ || (offset == cursor && i + 1 == count)
+ {
+ return child.leaf_at(cursor);
+ }
+ offset += len;
+ }
+
+ None
+ }
+
+ /// Find the rightmost contained non-trivia leaf.
+ pub fn rightmost_leaf(&self) -> Option<Self> {
+ if self.is_leaf() && !self.kind().is_trivia() {
+ return Some(self.clone());
+ }
+
+ for child in self.children().rev() {
+ if let Some(leaf) = child.rightmost_leaf() {
+ return Some(leaf);
+ }
+ }
+
+ None
+ }
+
+ /// Get the leftmost non-trivia leaf after this node.
+ pub fn next_leaf(&self) -> Option<Self> {
+ let mut node = self.clone();
+ while let Some(next) = node.next_sibling() {
+ if let Some(leaf) = next.leftmost_leaf() {
+ return Some(leaf);
+ }
+ node = next;
+ }
+ self.parent()?.next_leaf()
+ }
+}
+
+impl Deref for LinkedNode<'_> {
+ type Target = SyntaxNode;
+
+ fn deref(&self) -> &Self::Target {
+ self.get()
+ }
+}
+
+impl Debug for LinkedNode<'_> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.node.fmt(f)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::syntax::Source;
+
+ #[test]
+ fn test_linked_node() {
+ let source = Source::detached("#set text(12pt, red)");
+
+ // Find "text".
+ let node = LinkedNode::new(source.root()).leaf_at(7).unwrap();
+ assert_eq!(node.offset(), 5);
+ assert_eq!(node.len(), 4);
+ assert_eq!(node.kind(), &SyntaxKind::Ident("text".into()));
+
+ // Go back to "#set". Skips the space.
+ let prev = node.prev_sibling().unwrap();
+ assert_eq!(prev.offset(), 0);
+ assert_eq!(prev.len(), 4);
+ assert_eq!(prev.kind(), &SyntaxKind::Set);
+ }
+
+ #[test]
+ fn test_linked_node_non_trivia_leaf() {
+ let source = Source::detached("#set fun(12pt, red)");
+ let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
+ let prev = leaf.prev_leaf().unwrap();
+ assert_eq!(leaf.kind(), &SyntaxKind::Ident("fun".into()));
+ assert_eq!(prev.kind(), &SyntaxKind::Set);
+
+ let source = Source::detached("#let x = 10");
+ let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();
+ let prev = leaf.prev_leaf().unwrap();
+ let next = leaf.next_leaf().unwrap();
+ assert_eq!(prev.kind(), &SyntaxKind::Eq);
+ assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 0 });
+ assert_eq!(next.kind(), &SyntaxKind::Int(10));
+ }
+
+ #[test]
+ fn test_linked_node_leaf_at() {
+ let source = Source::detached("");
+ let leaf = LinkedNode::new(source.root()).leaf_at(0).unwrap();
+ assert_eq!(leaf.kind(), &SyntaxKind::Markup { min_indent: 0 });
+
+ let source = Source::detached("Hello\n");
+ let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
+ assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 1 });
+ }
+}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index c461a589..77411cf6 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -5,6 +5,7 @@ pub mod highlight;
mod incremental;
mod kind;
+mod linked;
mod node;
mod parser;
mod parsing;
@@ -14,6 +15,7 @@ mod span;
mod tokens;
pub use self::kind::*;
+pub use self::linked::*;
pub use self::node::*;
pub use self::parsing::*;
pub use self::source::*;